元组的定义和初始化,其元素具有相同的模板类,但具有不同的特化

问题描述 投票:1回答:2

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

我想要实现的是构建std::tuple维度N(编译时已知的N),其组件类型由模板类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< M, N=0 >std::tuple< MyType< M,0 > >;

DefineType< M, N=1 >std::tuple< MyType< M,0 >, MyType< M,1 > >;

DefineType< M, N=2 >: std::tuple< MyType< M,0 >, MyType< M,1 > , MyType< M,2 > >;

等等,直到一般的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<M,N>。基本上N没有达到基本情况,与N=0。我不明白为什么......所以确定递归类型定义是错误的。但是,除此之外,我可能还有其他错误。我希望你能帮助我理解如何做到这一点。我道歉,但元编程对我来说是非常新的(也很困难)。

谢谢。

c++ c++11 templates recursion template-meta-programming
2个回答
1
投票

我在你的代码中看到了两个问题。

(1)你说你想要那个

DefineType< M, N=2 >std::tuple< MyType< M,0 >, MyType< M,1 > , MyType< M,2 > >

但写作

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

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

DefineType< M, N=2 >std::tuple<MyType<M, 2>, MyType<M, 1> , MyType<M, 0>>

如果你想要从零到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 > 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); 
 }

2
投票

鉴于定义

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<M,N-1>(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

© www.soinside.com 2019 - 2024. All rights reserved.