我是c ++元编程的新手。我试着看看其他答案,但我找不到适合我的问题的答案。或者只是我无法将它应用于我的案例。在这里,我将发布代码的简化版本,以突出我想要获得的主要功能。
我想要实现的是构建std::tuple
维度N
(编译时已知的N
),其组件类型由模板类MyType
给出,取决于两个参数M
和N
。 M
是固定的,而元组组件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
。我不明白为什么......所以确定递归类型定义是错误的。但是,除此之外,我可能还有其他错误。我希望你能帮助我理解如何做到这一点。我道歉,但元编程对我来说是非常新的(也很困难)。
谢谢。
我在你的代码中看到了两个问题。
(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);
}
鉴于定义
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 ¶m, 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>()));