【问题标题】:Base case specialization for recursive variadic template递归可变参数模板的基本案例特化
【发布时间】:2020-09-29 11:40:40
【问题描述】:

我的目标是定义一个Recursive 类,以int N 和一个或多个类型T, ...Ts 为模板,其行为应类似于std::pair

  • N 中的 std::array 类型为 T 的项目first
  • 并且,作为second,一个可选 std::vectorRecursive 实例在相同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 << "general\n"; }
};

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

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>{}();
}

【问题讨论】:

  • 评论后验:由于std::vector 可以有零个元素,因此将其包装在可选中可能真的没有意义;一个空的std::vector 已经代表递归的结束。

标签: c++ c++17 variadic-templates template-meta-programming boost-hana


【解决方案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 &lt;int N, typename T1, typename T2, typename ...Ts&gt; struct Recursive; 至少需要 3 个参数。我认为它应该是一个专业化,主要模板应该是:

      template <int N, typename T1, typename ...Ts>
      struct Recursive;
      
    • template &lt;int N, typename T&gt; struct Recursive&lt;N, T&gt; 的行为不像 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));
    }
    
    

    Demo

    【讨论】:

    • +1。两个专业中的using 语句是什么?另外,当您说我需要转发基类的构造函数时,您指的是代码的哪一部分?最后,你想看看我的自我回答吗?我觉得你的(嗯,另一个)比我的好,但也许你可以给出更具体的意见?
    • using Base::Base; 允许复用基类构造函数,避免编写构造函数转发到基类。
    • 哇,但这太棒了!所以它被称为继承构造函数,基于cppreference。所以最后一个pair 是与std::pair 类关联的构造函数。嗯,很有趣也很有用!
    • 我对此只有最后一条评论(然后我可能会在此处链接我计划提出的后续问题,以防您也想查看)。我认为您可以将第一个专业化作为主要模板(通过删除T2 和删除&lt;N, T1, T2, Ts...&gt;),它无论如何都可以工作(verified)。这是疏忽还是故意的?
    • 我使用了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(Args&& ...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(Args&& ...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);
    
    }
    

    【讨论】:

    • 转发构造函数有一个警告,因为它比非 const 左值的复制构造函数更匹配。
    • @Jarod42,而您继承基类构造函数的解决方案没有这个或其他弱点,对吧?我不会删除我的答案,因为我认为它可能具有启发性,特别感谢您的评论。对于读者来说,在 SO 中使用[c++11] [perfect-forwarding] "forwarding constructor" 进行搜索会得到很多好的结果。
    猜你喜欢
    • 1970-01-01
    • 2012-04-29
    • 1970-01-01
    • 2017-02-23
    • 1970-01-01
    • 2017-10-15
    • 1970-01-01
    • 1970-01-01
    • 2017-07-13
    相关资源
    最近更新 更多