【问题标题】:Definition and Initialization of a tuple whose components are of the same templated class, but with different specialisations元组的定义和初始化,其组件属于同一模板类,但具有不同的特化
【发布时间】:2019-04-06 12:43:25
【问题描述】:

我是 C++ 元编程的新手。我试图查看其他答案,但找不到适合我的问题的答案。 或者只是我无法将它应用到我的案例中。 在这里,我将发布代码的简化版本,以突出显示我想要获得的主要功能。

我想要实现的是构造一个维度为NN 在编译时已知)的std::tuple, 其组件类型由模板类MyType 给出,取决于两个参数MNM 是固定的,而元组组件i 的类型实际上是MyType<M,i>,对于i=0,...,N。 因为我必须递归地定义这些类型的元组,所以我考虑了DefineType 模板。

// general definition
template<Integer M, Integer N>
struct DefineType
{
    using rest = typename DefineType<M, N-1>::type;
    using type = decltype(std::tuple_cat(std::declval< std::tuple< MyType<M,N>>>(),
                                         std::declval<rest>() ));
};

// specialization for N=0
template<Integer M>
struct DefineType<M,0>
{
 using type = typename std::tuple< MyType<M,0> >;
};

这应该产生以下类型:

DefineType&lt; M, N=0 &gt;:std::tuple&lt; MyType&lt; M,0 &gt; &gt;;

DefineType&lt; M, N=1 &gt;:std::tuple&lt; MyType&lt; M,0 &gt;, MyType&lt; M,1 &gt; &gt;;

DefineType&lt; M, N=2 &gt;: std::tuple&lt; MyType&lt; M,0 &gt;, MyType&lt; M,1 &gt; , MyType&lt; M,2 &gt; &gt;;

以此类推,直到一般N

然后我还想根据我称之为param 类型Param 的东西来初始化这种类型的元组。为此, 我写了这样的代码:

// general definition
template<Integer M, Integer N>
typename DefineType<M,N>::type  MyClass(Param param)
{
    return std::tuple_cat(std::tuple<MyType<M,N>>(MyType<M,N>(param)), 
                                  MyClass<M,N-1>(param)   )   ;
}
// specialization for N=0
template<Integer M>
typename DefineType<M,0>::type MyClass(Param param)
{
    return std::tuple<MyType<M, 0>>(MyType<M, 0>(param));
}

终于进入正题了:

int main()
{

// M and N given
const auto myobject=MyClass<M,N>(param);

}

代码没有编译,抱怨我初始化太多次DefineType&lt;M,N&gt;。基本上N 没有达到基本情况,N=0。我不明白为什么......所以肯定递归类型定义是错误的。但是,除此之外,也许还有其他我看不到的错误。我希望你能帮助我理解如何做到这一点。我很抱歉,但元编程对我来说是非常新的(也很困难)。

谢谢。

【问题讨论】:

  • 您只需要 C++11 的答案,还是也接受 C++14/C++17 的解决方案?

标签: c++ c++11 templates recursion template-meta-programming


【解决方案1】:

根据定义

template<Integer M, Integer N>
typename DefineType<M,N>::type  MyClass(Param param)
{
    return std::tuple_cat(std::tuple<MyType<M,N>>(MyType<M,N>(param)), 
                                  MyClass<M,N-1>(param)   )   ;
}
template<Integer M>
typename DefineType<M,0>::type MyClass(Param param)
{
    return std::tuple<MyType<M, 0>>(MyType<M, 0>(param));
}

您拥有的是两个重载的不同函数模板。第二个不是第一个的“部分特化”,因为没有函数模板部分特化,只有类模板特化。 (所以调用 MyClass&lt;M,N-1&gt;(param) 不可能匹配第二个模板,即使它之前已经声明过,因为第二个模板只接受一个模板参数,这意味着第一个模板是无限递归的。)

一种解决方案是使用帮助类模板:

namespace MyClass_detail {
    template<Integer M, Integer N>
    struct helper {
        static typename DefineType<M,N>::type build(const Param& param)
        {
            return std::tuple_cat(
                std::tuple<MyType<M,N>>(MyType<M,N>(param)), 
                MyClass<M,N-1>(param));
        }
    };

    template<Integer M>
    struct helper<M, 0> {
        static typename DefineType<M,0>::type build(const Param& param)
        {
            return std::tuple<MyType<M, 0>>(MyType<M, 0>(param));
        }
    };
}

template<Integer M, Integer N>
typename DefineType<M,N>::type MyClass(Param param)
{
    return MyClass_detail::helper<M,N>::build(Param);
}

虽然我会建议利用std::make_integer_sequence。 (这是一个 C++14 功能,我看到你的问题被标记为 C++11。如果你不能使用 C++14 或更高版本,搜索应该会找到make_integer_sequence 和相关工具的一些替代实现可以在 C++11 中使用。)

#include <utility>
#include <tuple>

namespace MyClass_detail {
    template<Integer M, Integer N, Integer ...Inds>
    auto MyClass_helper(const Param &param, std::integer_sequence<Integer, Inds...>)
    {
        return std::make_tuple(MyType<M, N-Inds>(param)...);
    }
}

template<Integer M, Integer N>
auto MyClass(Param param)
{
    return MyClass_detail::MyClass_helper<M,N>(
        param, std::make_integer_sequence<Integer, N+1>{});
}

// And if DefineType is wanted for other uses:
template<Integer M, Integer N>
using DefineType = decltype(MyClass<M,N>(std::declval<Param>()));

请参阅full working demo on coliru

【讨论】:

    【解决方案2】:

    我发现您的代码中有两个问题。

    (1) 你说你想要那个

    DefineType&lt; M, N=2 &gt;std::tuple&lt; MyType&lt; M,0 &gt;, MyType&lt; M,1 &gt; , MyType&lt; M,2 &gt; &gt;

    但写作

    using type = decltype(std::tuple_cat(std::declval< std::tuple< MyType<M,N>>>(),
                                         std::declval<rest>() ));
    

    DefineType 内,你得到相反的顺序;你得到那个

    DefineType&lt; M, N=2 &gt;std::tuple&lt;MyType&lt;M, 2&gt;, MyType&lt;M, 1&gt; , MyType&lt;M, 0&gt;&gt;

    如果您想要从零到N 的顺序,您必须在DefineType 中定义rest 之前,然后是N 元素;我是说

    using type = decltype(std::tuple_cat(
          std::declval<rest>(),
          std::declval<std::tuple<MyType<M,N>>>() ));
    

    (2) MyClass() 函数的递归不起作用,因为在您的递归版本中,使用 两个 模板参数调用相同的 MyClass()

    template<Integer M, Integer N>
    typename DefineType<M,N>::type  MyClass(Param param)
    {
        return std::tuple_cat(std::tuple<MyType<M,N>>(MyType<M,N>(param)), 
                                      MyClass<M,N-1>(param)   )   ;
    } // you call the second parameter .........^^^
      // also when N is 1 (and N-1 is 0)
    

    因此基本情况(仅使用一个模板参数定义)永远不会匹配。

    不幸的是,部分模板特化不适用于模板函数,因此您可以使用结构的部分模板特化(请参阅 aschepler 的答案),或者,如果您愿意,可以使用 SFINAE 根据值启用/禁用 MyClass() 的两个版本N.

    我提出以下解决方案

    // specialization for N == 0
    template <Integer M, Integer N>
    typename std::enable_if<(N == 0), typename DefineType<M,0>::type>::type
       MyClass(Param param)
     { return std::tuple<MyType<M, 0>>(MyType<M, 0>(param)); }
    
    // general definition
    template <Integer M, Integer N>
    typename std::enable_if<(N > 0u), typename DefineType<M,N>::type>::type
       MyClass(Param param)
    {
        return std::tuple_cat(
           MyClass<M,N-1>(param),
           std::tuple<MyType<M,N>>(MyType<M,N>(param)) );
    }
    

    请注意,现在基本情况 (N == 0) 有两个模板参数,但仅当 N 为零时才启用。其他情况仅在N &gt; 0 时启用。

    还要注意,您必须在基本案例版本之前编写,因为它被递归版本使用。

    请注意我已经切换了休息/实际类型的顺序。

    如果你可以使用 C++14,那么std::make_index_sequence/std::index_sequence,我强烈建议避免递归并遵循 aschepler 的建议。

    您还可以使用以下特化来避免 DefineType 本身的递归

    template <Integer, Integer N, typename = std::make_index_sequence<N+1u>>
    struct DefineType;
    
    template <Integer M, Integer N, std::size_t ... Is>
    struct DefineType<M, N, std::index_sequence<Is...>>
     { using type = std::tuple<MyType<M, Is>...>; };
    

    以下是完整的编译C++14示例

    #include <tuple>
    #include <type_traits>
    
    using Integer = std::size_t;
    using Param = int;
    
    template <Integer M, Integer N>
    struct MyType
     { MyType (Param) {} };
    
    template <Integer, Integer N, typename = std::make_index_sequence<N+1u>>
    struct DefineType;
    
    template <Integer M, Integer N, std::size_t ... Is>
    struct DefineType<M, N, std::index_sequence<Is...>>
     { using type = std::tuple<MyType<M, Is>...>; };
    
    template <Integer M, Integer N>
    std::enable_if_t<(N == 0), typename DefineType<M,0>::type>
       MyClass(Param param)
     { return std::tuple<MyType<M, 0>>(MyType<M, 0>(param)); }
    
    // general definition
    template <Integer M, Integer N>
    std::enable_if_t<(N > 0u), typename DefineType<M,N>::type>
       MyClass(Param param)
    {
        return std::tuple_cat(
           MyClass<M,N-1>(param),
           std::tuple<MyType<M,N>>(MyType<M,N>(param)) );
    }
    
    int main ()
     {
       using t0 = typename DefineType<42u, 0u>::type;
       using u0 = std::tuple<MyType<42u, 0u>>;
       using t1 = typename DefineType<42u, 1u>::type;
       using u1 = std::tuple<MyType<42u, 0u>, MyType<42u, 1u>>;
       using t2 = typename DefineType<42u, 2u>::type;
       using u2 = std::tuple<MyType<42u, 0u>, MyType<42u, 1u>, MyType<42u, 2u>>;
    
       static_assert( std::is_same<t0, u0>::value, "!" );
       static_assert( std::is_same<t1, u1>::value, "!" );
       static_assert( std::is_same<t2, u2>::value, "!" );
    
       auto const myobject = MyClass<42u, 2u>(12); 
     }
    

    【讨论】:

    • std::enable_if_t 是 C++14;要与 C++11 兼容,我们必须使用 typename enable_if&lt;...&gt;::type
    • @aschepler - 是的......显然......我已经写了一个 C++14 解决方案,但我忘记更正了;谢谢。
    • 第 1 点)。是的,我忘了更改顺序,但这似乎不是那么重要的事情。第 2 点)。谢谢,我不知道,现在可以了!添加了 +1,但不可见,因为这是我的第一篇文章...
    • @Garo 如果它解决了您的问题,请务必将其标记为答案。
    • 可以在没有 SFINAE 的情况下定义“MyClass”。观察到 'DefinedType' 实际上返回一个元组,只需 'return make_tuple(MyType(param)...);'。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-04-23
    • 1970-01-01
    • 1970-01-01
    • 2012-08-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多