Специализация базового варианта для рекурсивного вариативного шаблона

#c #c 17 #variadic-templates #template-meta-programming #boost-hana

#c #c 17 #variadic-шаблоны #шаблон-метапрограммирование #boost-hana

Вопрос:

Моя цель — определить Recursive класс, созданный по шаблону для int N и одного или нескольких типов T, ...Ts , который должен вести себя как std::pair с

  • a std::array N элементов типа T как first ,
  • и, как second , необязательный std::vector из Recursive экземпляров, шаблонизированных на том же N и на остальных аргументах шаблона Ts... .

Пытаясь записать класс с учетом вышеуказанных требований, я придумал этот нерабочий код (где я также определил некоторые необходимые, поскольку они очень помогают, псевдонимы для двух экземпляров Recursive ), и я не знаю, не ошибся ли я в том, что я описал выше (или если это неверно составленное описание!), Или если я неправильно использую синтаксис языка.

 #include <array>
#include <boost/hana/fwd/optional.hpp>
#include <boost/hana/optional.hpp>
#include <string>
#include <utility>
#include <vector>

template <int N, typename T1, typename T2, typename ...Ts>
struct Recursive
    : std::pair<std::array<T1, N>, 
                boost::hana::optional<std::vector<Recursive<N, T2, Ts...>>>> {};

template <int N, typename T>
struct Recursive<N, T> : std::array<T, N> {};

template<typename ...T>
using Recursive2 = Recursive<2u, T...>;

template<typename ...T>
using Recursive3 = Recursive<3u, T...>;

int main() {
    using boost::hana::nothing;
    Recursive2<int> x(std::make_pair(std::array<int, 2>{0,0}, nothing));
}
  

Я добавлю некоторые способы устранения неполадок, которые я уже сделал. В дальнейшем спецификация шаблона, похоже, работает просто отлично.

 #include <iostream>

template <int N, typename T, typename ...Ts>
struct Recursive {
    void operator()(){ std::cout << "generaln"; }
};

template <int N, typename T>
struct Recursive<N, T> {
    void operator()(){ std::cout << "specializedn"; }
};

template<typename ...T>
using Recursive2 = Recursive<2u, T...>;

template<typename ...T>
using Recursive3 = Recursive<3u, T...>;

int main() {
    Recursive2<int>{}();
    Recursive2<int>{}();
    Recursive2<int,int>{}();
}
  

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

1.Комментарий апостериори: поскольку std::vector может иметь нулевые элементы, вероятно, действительно нет смысла оборачивать его в необязательный; пустой std::vector уже представляет конец рекурсии.

Ответ №1:

Ваша ошибка заключается в том, что сначала вы объявили Recursive получение по крайней мере одного целого числа и двух или более типов, а затем вы объявили частичную специализацию, получающую одно целое число и ровно один тип.

Ошибка, поскольку специализация не может принимать только один тип, когда объявлен основной шаблон, принимающий два или более типов.

Может показаться нелогичным, но решением может быть объявление Recursive получения только одного типа или более (и это становится основным случаем рекурсии) и специализация, получающая два типа или более

 template <int N, typename T1, typename...>
struct Recursive : std::array<T1, N>
 { };

template <int N, typename T1, typename T2, typename ...Ts>
struct Recursive<N, T1, T2, Ts...>
   : std::pair<std::array<T1, N>,
               boost::hana::optional<std::vector<Recursive<N, T2, Ts...>>>>
 { };
  

Следующий немного изменен ( std::size_t вместо int для размеров; std::optional вместо boost::hana::optional ), но полностью компилирующий пример

 #include <array>
#include <optional>
#include <string>
#include <utility>
#include <vector>

template <std::size_t N, typename T1, typename...>
struct Recursive : std::array<T1, N>
 { };

template <std::size_t N, typename T1, typename T2, typename ...Ts>
struct Recursive<N, T1, T2, Ts...>
   : std::pair<std::array<T1, N>,
               std::optional<std::vector<Recursive<N, T2, Ts...>>>>
 { };

template<typename ...T>
using Recursive2 = Recursive<2u, T...>;

template<typename ...T>
using Recursive3 = Recursive<3u, T...>;

int main ()
 {
    Recursive2<int> x{std::array<int, 2u>{0,0}};
    Recursive3<int, long> y{{std::array<int, 3u>{0,0,0}, {}}};
 }
  

Ответ №2:

У вас есть несколько проблем:

  • Ваша специализация не соответствует вашему основному шаблону

    template <int N, typename T1, typename T2, typename ...Ts> struct Recursive; требуется не менее 3 параметров. Я думаю, что это должна быть специализация, а основной шаблон должен быть:

     template <int N, typename T1, typename ...Ts>
    struct Recursive;
      
  • template <int N, typename T> struct Recursive<N, T> не ведет себя как a std::pair (поскольку вы указываете свое требование, иначе ваше использование неверно), вы, вероятно, хотите что-то вроде:

     template <int N, typename T>
    struct Recursive<N, T> : std::pair<std::array<T, N>, decltype(boost::hana::nothing)>
      
  • Вам нужно «переслать» конструкторы базового класса (композиция вместо наследования тоже может быть вариантом, или черты для определения используемого типа) или изменить способ построения объекта.

Результат:

 template <int N, typename T1, typename ...Ts>
struct Recursive;

template <int N, typename T1, typename T2, typename ...Ts>
struct Recursive<N, T1, T2, Ts...>
    : std::pair<std::array<T1, N>,
                boost::hana::optional<std::vector<Recursive<N, T2, Ts...>>>
                            >
{
    using std::pair<
        std::array<T1, N>,
        boost::hana::optional<std::vector<Recursive<N, T2, Ts...>>>>::pair;
};

template <int N, typename T>
struct Recursive<N, T>
    : std::pair<std::array<T, N>, decltype(boost::hana::nothing)>
{
    using std::pair<std::array<T, N>, decltype(boost::hana::nothing)>::pair;
};

template<typename ...T>
using Recursive2 = Recursive<2u, T...>;

template<typename ...T>
using Recursive3 = Recursive<3u, T...>;

int main() {
    using boost::hana::nothing;
    Recursive2<int> x(std::make_pair(std::array<int,2>{0,0}, nothing));
}

  

ДЕМОНСТРАЦИЯ

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

1. 1. Каковы using операторы внутри двух специализаций? Кроме того, на какую часть вашего кода вы ссылаетесь, когда говорите, что мне нужно переслать конструкторы базового класса ? Наконец, вы хотите взглянуть на мой собственный ответ? Я чувствую, что ваш (ну, и другой) лучше моего, но, может быть, вы могли бы высказать более конкретное мнение?

2. using Base::Base; Позволяет повторно использовать базовый конструктор и избегать записи конструктора для пересылки в базовый класс.

3. Вау, но это потрясающе! Поэтому он известен как наследование конструкторов , основанных на cppreference . Таким образом, последним pair в строке является коструктор, связанный с std::pair классом. Ммм, очень интересно и полезно!

4. У меня есть только один последний комментарий по этому поводу (тогда я, вероятно, приведу здесь следующий вопрос, который я планирую задать, на случай, если вы тоже захотите взглянуть на это). Я думаю, вы можете сделать первую специализацию основным шаблоном (путем удаления T2 и удаления <N, T1, T2, Ts...> ), и он все равно будет работать ( проверено ). Это упущение или это было преднамеренно?

5. Я использовал T2 его так, как вы изначально его использовали, не заметил, что он не нужен.

Ответ №3:

Я добавляю свой собственный ответ, потому что я нашел решение (немного раньше, чем получил два ответа; использование std::optional — это последнее изменение, которое я внес в соответствии с одним из ответов). Однако в моем решении мне пришлось объявить и определить конструктор для общих и специализированных шаблонов, что заставляет меня думать, что это не такое хорошее решение, как другие ответы. Но почему бы не опубликовать его?

 #include <cassert>
#include <iostream>
#include <array>
#include <optional>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>

template <int N, typename T, typename ...Ts>
struct Recursive : std::pair<std::array<T, N>,
                             std::optional<std::vector<Recursive<N, Ts...>>>
                   > {
    template<typename ...Args>
    Recursive(Argsamp;amp; ...args) : std::pair<std::array<T, N>,
                                std::optional<std::vector<Recursive<N, Ts...>>>
                  >(args...) {}
};

template <int N, typename T>
struct Recursive<N, T> : std::array<T, N> {
    template<typename ...Args>
    Recursive(Argsamp;amp; ...x) : std::array<T, N>(x...) {}
};


template<typename ...T>
using Recursive2 = Recursive<2u, T...>;

template<typename ...T>
using Recursive3 = Recursive<3u, T...>;

int main() {
    std::array<std::string, 2> twoStrings{"hello","Hello"};
    std::array<char, 2> twoChars{'h', 'H'};

    Recursive2<std::string> s{twoStrings};
    assert(s == twoStrings);

    std::vector<Recursive2<char>> vecOfTwoChars{twoChars, twoChars, twoChars};

    Recursive2<std::string, char> sc{twoStrings, vecOfTwoChars};
    assert(sc.first == twoStrings);
    assert(sc.second->size() == 3);
    assert(sc.second == vecOfTwoChars);
    assert(sc.second.value()[0] == twoChars);

}
  

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

1. Конструктор пересылки имеет предостережение, поскольку он лучше соответствует, чем конструктор копирования для неконстантного значения.

2. @Jarod42, в то время как ваше решение о наследовании конструктора базового класса не имеет этого или других недостатков, верно? Я не буду удалять свой ответ, так как думаю, что он может быть поучительным, особенно благодаря вашему комментарию. Для читателя поиск в SO с [c 11] [perfect-forwarding] "forwarding constructor" помощью дает много хороших результатов.