Объявление шаблона с переменными значениями из одного целочисленного аргумента?

#c #templates #sfinae #variadic

#c #шаблоны #sfinae #variadic

Вопрос:

Возможно ли статически объявить N однотипных аргументов шаблона из одного аргумента шаблона целочисленного типа? Потенциально что-то похожее на это:

 template < int N >
class MyTemplatedType {
    // base type, known
    typedef float base_t;
    // instance of some other templated class with *N* template arguments
    SomeOtherClass< base_t, base_t, base_t, base_t, ..., base_t > foo;
    /* ... */
}
 

Я понимаю, что могу использовать шаблон variadic и использовать его непосредственно для объявления экземпляра члена, но мне было интересно, будет ли какая-то реализация SFINAE, которая разрешала бы один целочисленный параметр шаблона, поскольку синтаксис был бы намного чище и интуитивно понятен.

Ответ №1:

Вы можете написать мета-функцию, которая принимает N , base_t , и SomeOtherClass , и рекурсивно вызывает себя с меньшим N значением, каждый раз переходя base_t к концу растущего пакета параметров:

 template <int N, typename T, template<typename...> typename C, typename ...Ts>
struct expand { 
    using type = typename expand<N-1, T, C, T, Ts...>::type;
};
 

Для базового случая, когда N значение уменьшается до 0, мета-функция выдает SomeOtherClass экземпляр с набором параметров N base_t типов:

 template <typename T, template<typename...> typename C, typename ...Ts>
struct expand<0, T, C, Ts...> { 
    using type = C<Ts...>;
};
 

Кроме того, удобно использовать псевдоним, чтобы избежать необходимости указывать typename на сайте вызова:

 template <int N, typename T, template<typename...> typename C>
using expand_t = typename expand<N, T, C>::type;
 

Теперь на сайте вызова вы можете написать:

 expand_t<N, base_t, SomeOtherClass> foo; 
// equivalent to 
// SomeOtherClass< base_t, base_t, ..., base_t > foo;
//               ^^^        N times          ^^^ 
 

Вот демонстрация.

Комментарии:

1. Это именно то, что я искал. Некоторые замечания, хотя 1) Я понимаю, что пакет параметров ...Ts кодирует рекурсивно растущий список аргументов шаблона, но зачем именно нам нужно передавать второй T аргумент при рекурсивном вызове, 2) что изменится, если попытаться использовать пакет параметров в качестве аргумента функции вместо аргумента шаблона, например void myFunction(const floatamp; args...) ?

2. 1) Вот как Ts... создается новое в рекурсивном вызове: from T, Ts... . 2) Я не уверен, что вы подразумеваете под «вместо аргумента шаблона». Показанное вами объявление, по крайней мере, на C , недопустимо.

3. Ах, хорошо, теперь это имеет смысл. Что касается 2) я имел в виду, как мне распаковать пакет параметров при объявлении функции, которая принимает N аргументы типа T ?

4. Я думаю, вы спрашиваете о «расширении пакета». Найдите этот термин и посмотрите, отвечает ли он на ваш вопрос.

5. Я немного знаком с расширением пакета параметров, моя точка зрения заключалась в том, как настроить ваше решение для кодирования только параметра переменной длины, а не специализации шаблона класса C , чтобы я мог объявить функцию void someFunction(expand<5, base_t>... args) или что-то подобное.

Ответ №2:

Одно из возможных решений — использование кортежей и вспомогательных шаблонов для сборки нужного класса. Немного дополнительного сахара может сделать результирующий синтаксис «более чистым», протестированный с помощью gcc 10.2:

 #include <tuple>
#include <type_traits>

// Create std::tuple<T ...>, with T repeated N times.

template<int N, typename T>
struct tuple_list : tuple_list<N-1, T> {

    typedef decltype(std::tuple_cat(std::declval<typename tuple_list<N-1, T>
                    ::tuple_t>(),
                    std::declval<std::tuple<T> amp;amp;>())
             ) tuple_t;
};

template<typename T>
struct tuple_list<0, T> {

    typedef std::tuple<> tuple_t;
};

template<typename ...> struct SomeOtherClass {};

// And now, replace `std::tuple` with SomeOtherClass 

template<typename tuple_t> struct make_some_other_class;

template<typename ...Args>
struct make_some_other_class<std::tuple<Args...>> {

    typedef SomeOtherClass<Args...> type_t;
};

template < int N >
class MyTemplatedType {

    typedef float base_t;

public:

    // The payload is not exactly "clean", but one more helper
    // template can make the syntax here a little bit nicer...

    typename make_some_other_class< typename tuple_list<N, base_t>
                    ::tuple_t >::type_t foo;
};

void foobar(MyTemplatedType<3> amp;bar)
{
    SomeOtherClass<float, float, float> amp;this_works=bar.foo;
}