LINUX.ORG.RU

C++. Метапрограммирование. Ненависть.

 , ,


0

3

Раз:

class C {
    ...

    // Специализации ниже, вне класса.
    template<class T> void fOne(T x);

    // - Не-шаблонный метод с пустым списком параметров не вызовется из F::call<TT...>() при пустом списке аргументов:
    //   оно будет искать fMany<>(), а не fMany().
    // - Шаблон с пустым списком аргументов сделать нельзя: оно будет считать синтаксис "template<>"
    //   специализацией несуществующего шаблона.
    // - Если в теле класса объявить "template<class... TT>", то специализация "template<> void fMany<>() {}" возьмётся,
    //   но "template<class T, class TT...>" будет считаться частичной специализацией, что для функций запрещено.
    // Остаётся эмулировать шаблон с пустым множеством аргументов (т.е. "sizeof...(RR) == 0") через enable_if.

    template<class... TT, std::enable_if_t<(sizeof...(TT) == 0), bool> = true> void fMany() {}

    template<class T, class... TT> inline void fMany(T arg, TT... args) {
        fOne(arg);
        fMany(args...);
    }
}

★★★★★

Блин, случайно запостил раньше времени. Ладно, обойдёмся без «два». И так «хорошо». Полнейшая мерзотная мерзость этот ваш сраный C++. Ура блин: я ухитрился обойтись без десятиэтажного мата.

dimgel ★★★★★
() автор топика

Когда вместо того что-бы просто писать код придумывают вещи которые генерируют код за них, а потом героически сражаются с тем чего быть не должно. Используй просто макросы если тебе в падлу лишних строк десяток написать или пиши всё сам как и положено. Хероту эту ущербную забудь.

Господи во что переросло программирование, вместо делания дел мастурбируют на сам язык. И тупые вещи вроде мета-блабла.

anonymous
()
Ответ на: комментарий от anonymous

Спасибо. Только прозрел я уже лет 10 назад, и всё это время топлю за AST-макросы. Мне следовало бы «метапрограммирование» в заголовке взять в кавычки.

dimgel ★★★★★
() автор топика
Ответ на: комментарий от anonymous

Когда вместо того что-бы просто писать код придумывают вещи которые генерируют код за них,

Что тут думать, трясти надо! (с)


А ты, никак, на голом асме пишешь? Сишка же за тебя код генерирует, не по пацански получается…

Siborgium ★★★★★
()
Ответ на: комментарий от Siborgium

Хотя в одном из кейсов придётся оставить старый код – где функции возвращают tuple с обратным порядком значений, вернее порядок как указано в шаблоне, но с некоего стека значения снимаются и помещаются в tuple в обратном порядке.

dimgel ★★★★★
() автор топика
Ответ на: комментарий от Siborgium

Хз что такое MVE, а код вот (в ОП s/f/push/g, а здесь pop):

template<class R> R popOne();

template<class... RR, std::enable_if_t<(sizeof...(RR) == 0), bool> = true> std::tuple<RR...> popMany() {
	return std::tuple<>();
}

template<class R, class... RR> std::tuple<R, RR...> popMany() {
	auto rr = popMany<RR...>();
	auto r = std::tuple<R>(popOne<R>());
	return std::tuple_cat(r, rr);
}

И ещё одна большая печаль у меня была (ненаписанный п.2, даже п.3) – enable_if во внутреннем шаблоне не работает по аргументам внешнего. Имеется что-то такое:

template<class... RR> class F {
	template <class... TT> std::tuple<RR...> call(TT... args) { ... }
}

Хотелось, чтобы при (sizeof…(RR) == 1) инстанциировался не «tuple<RR…> call()», а «R call()» – но enable_if ругается длинно и невразумительно (что-то там типа type у него неопределён). Так что до вопроса – что писать вместо R, и покатит ли «RR… call()» – дело даже не дошло.

dimgel ★★★★★
() автор топика
Ответ на: комментарий от Siborgium

index_sequence

Любопытно. Посмотрю поподробнее на свежую голову.

dimgel ★★★★★
() автор топика
Ответ на: комментарий от dimgel

На чистом С++20 можно было бы еще проще.

#include <iostream>
#include <stack>
#include <tuple>
#include <type_traits>
#include <utility>

template <typename T, typename ... Ts>
struct first {
    using type = T;
};

template <typename ... Ts>
using first_t = typename first<Ts...>::type;

template <typename ... Rs>
struct S {
    std::stack<int> s_;

    auto pushOne(auto x) {
        s_.push(x);
        std::cout << "pushed '" << x << "'\n";
    }
    auto pushMany(auto ... xs) { (pushOne(xs), ...); }
    auto popOne() {
        auto e = s_.top();
        s_.pop();
        std::cout << "popped '" << e << "'\n";
        return e;
    }

    // я так понимаю, С++20 у тебя нет, так что сразу вынесу в хелпер
    template <std::size_t ... I>
    auto pop_(auto && f, std::index_sequence<I...>) {
        // можно и без этого, но тогда придется принимать индекс в f
        auto wrap = [f](auto){ return f(); };
        return std::tuple{ wrap(I)... };
    }

    auto popMany() {
        constexpr auto seq = std::make_index_sequence<sizeof...(Rs)>{};
        return pop_([this](){ return this->popOne(); }, seq);
    }
    
    // можно так
    auto call(auto ... args) -> std::conditional_t<sizeof ... (Rs) == 1, first_t<Rs...>, std::tuple<Rs...>>;

    // а лучше так
    auto call(auto ... args) {
        if constexpr (sizeof ... (Rs) == 1) {
            // return ...
        } else {
            // return std::tuple{ ... }
        }
    }
};

int main() {
    // шаблонные параметры указал явно ради простоты
    S<int, int, int> s;
    s.pushMany(1, 2, 3, 4, 5, 6, 7);
    auto tuple = s.popMany();
    static_assert(std::is_same_v<decltype(tuple), std::tuple<int, int, int>>);
}

Вывод:

pushed '1'
pushed '2'
pushed '3'
pushed '4'
pushed '5'
pushed '6'
pushed '7'
popped '7'
popped '6'
popped '5'

Да, не забудьте добавить static_assert на совпадение размерностей параметр паков Rs и Ts в pushMany. Как можно видеть, я складываю в стек 7 чисел, но достаю только 3 – static_assert (и, возможно – не знаю вашей конкретики – deduction guide) поможет это предотвратить.

Siborgium ★★★★★
()

Каждый раз когда я сталкивался с 10-этажными конструкциями в С++, я всегда находил как это сделать по-другому и более проще.

Проблема С++ в том, что ты одно и то же можешь написать десятью способами. И получается что только от тебя зависит, насколько код будет простым и читаемым.

Это как с велосипеда пересесть в самолет с сотнями тублерами-крутилками, и материться что всё очень сложно.

Kroz ★★★★★
()

я не понял задачу или что?

struct C {
  template <typename T>
  void f1(T x) {
    std::cout << x << std::endl;
  }

  void f() {std::cout << "empty" << std::endl;}

  template <typename T>
  void f(T x) {
    f1(x);
    f();
  }

  template <typename T, typename ...R>
  void f(T x, R...rest) {
    f1(x);
    f<R...>(rest...);
  }
};

int main() {
  C c;
  c.f(1,2,3);
  c.f();
}

anonymous
()
Ответ на: комментарий от Kroz

В теории согласен, но как известно, «the difference between theory and practice…». Я очень долго обходился без всей этой шаблонной дичи (используемый мною синтаксис enable_if вчера за целый день так и не понял; авось щас ответ Siborgium-а пойму), но увы, вляпался-таки в конце концов в её необходимость. Будь на плюсах МП, не вляпался бы – тупо и примитивно императивно сделал бы генерацию нужного мне кода, и так и продолжал бы обходить всю эту стоэтажную пое@#нь десятой дорогой.

dimgel ★★★★★
() автор топика
Последнее исправление: dimgel (всего исправлений: 1)
Ответ на: комментарий от Kroz

Вообще, есть совершенно роскошное видео, наглядно демонстрирующее, как желание писать эффективный код (для чего собственно C++ и предназначен) заводит в дебри ужаса: https://youtu.be/PNRju6_yn3o

Я, – говорит, – пишу книги по C++ потому что каждый раз забываю, как он работает. Гыгы. Квинтессенция.

dimgel ★★★★★
() автор топика
Ответ на: комментарий от dimgel

Ну собственно идея простая. Есть параметр паки. Их можно передавать куда-то, применять функции к их элементам. Соответственно, pushMany реализуется элементарно.

С popMany немного сложнее – нет пака, к которому можно было бы применить функцию, но известна длина. По этой длине строим пак индексов с помощью index_sequence. К каждому элементу этого пака применяем функцию, возвращающую popOne, и получаемый пак элементов передаем в конструктор std::tuple, у которого еще и подходящий deduction guide есть, так что писать параметры руками не нужно.

Если есть С++20, то хелпер не нужен, на месте пишется лямбда – им разрешили иметь шаблонные параметры.

Siborgium ★★★★★
()
Последнее исправление: Siborgium (всего исправлений: 2)
Ответ на: комментарий от Siborgium

Ну собственно идея простая.

Идею-то я понял с одного названия index_sequence. :)

Если есть С++20, то хелпер не нужен, на месте пишется лямбда – им разрешили иметь шаблонные параметры.

Ага, вон оно чё. Как раз думал спросить. :)

dimgel ★★★★★
() автор топика
Ответ на: комментарий от Siborgium

Так-с, вроде осилил.

За auto + constexpr if спасибо ваще преогромное: хрен бы я сам догадался. За first + first_t и index_sequence тоже: пригодится. И вроде всё смутно знакомое (интересно откуда), но без примера не сложилось бы.

Ты используешь index_sequence, как я понимаю, ради избавления от рекурсивного шаблона <R, RR…>. При этом порядок значений в возвращамом тупле – от вершины стека, а мне надо наоборот. Т.е. после push 5, push 6, push 7 – F<три инта>::call() должен вернуть не tuple(7,6,5), а tuple(5,6,7). А я, честно говоря, сходу понадеялся, что у этого index_sequence есть стандартный способ развернуть последовательность (особо не задумываясь наперёд, как именно это мне поможет). Да и по компактности кода разницы не наблюдается.

dimgel ★★★★★
() автор топика
Ответ на: комментарий от dimgel

Вообще, у тебя что-то не так с дизайном. Ты из стека (lifo) делаешь очередь (fifo). Кроме того, у тебя в дизайне заложено, что храниться будут гетерогенные типы – это действительно необходимо?

Можно сделать так

    template <typename T, std::size_t ... I>
    auto pop_(T & tuple, std::index_sequence<I...>) {
        constexpr auto N = std::tuple_size_v<T>;
        ((std::get<N - I - 1>(tuple) = popOne()), ...);
    } 

    auto popMany() { 
        constexpr auto N = sizeof...(Rs);
        constexpr auto seq = std::make_index_sequence<N>{};
        std::tuple<Rs...> t;
        pop_(t, seq); 
        return t; 
    } 

// later

    std::cout << std::get<0>(tuple) << ' ' << std::get<1>(tuple) << ' ' << std::get<2>(tuple) << '\n'; // 5 6 7

Но это подразумевает наличие дефолтного конструктора и move/copy-assignment оператора (впрочем, последнее можно заменить свапом).

Способа развернуть действительно нет (он нужен был бы не у index_sequence, а непосредственно у параметр пака), это неприятно. Есть вариант создавать промежуточный тупл, который потом разворачивать. Это снимет требования выше (останется только необходимость наличия move-ctor), но это лишние действия.

Siborgium ★★★★★
()
Последнее исправление: Siborgium (всего исправлений: 1)
Ответ на: комментарий от Siborgium

Вообще, у тебя что-то не так с дизайном.

Не у меня. :)

UPD. А вообще, дизайн как дизайн. Так-не так – вопрос философский.

dimgel ★★★★★
() автор топика
Последнее исправление: dimgel (всего исправлений: 1)
Ответ на: комментарий от Siborgium

Видел и этот, и luapp. Не хочу пока что.

dimgel ★★★★★
() автор топика
Ответ на: комментарий от Kroz

Каждый раз когда я сталкивался с 10-этажными конструкциями в С++, я всегда находил как это сделать по-другому и более проще.

Ви-таки будете смеяться, но я теперь (почти… в процессе…) снимаю со стека все результаты разом. Не только проще, но и гораздо (не скажу почему) эффективнее.

dimgel ★★★★★
() автор топика
Ответ на: комментарий от dimgel

Пиши на Поцкале! Чего ныть то?

anonymous
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.