模板中的无限递归
Infinite recursion in templates
假设我有一个名为 List
的代理 class,它只不过是一堆 typename
s:
的持有者
template<typename... items> class List {
constexpr size_t SizeOf = /*Magic code that determines the length*/;
};
假设我有另一个 class 应该带到 Lists
并输出一个版本,其中较小的版本用 null_t
s:
填充
template<class flist,class slist>
class Pad{
typedef /*Undertermined*/ Flist;
typedef /*Undertermined*/ Slist;
};
唯一真正的问题是破坏递归....通常在模板递归中你只需专门化然后你就可以结束它。
这里有点不同,因为没有办法(至少我能看到)通过模板减速来区分两个列表之间的区别。
我尝试使用 std::conditional
来结束循环,但这不起作用。
这是一个例子:
template<int x>
class Mine{
typedef std::conditional<x == 12, Mine<x>::value, Mine<x+1>::value> value;
};
即使我有 x==12
条件,它仍然需要(或想要)清除 Mine<x+1>::value
。
那么这种情况的一般策略是什么?
在第二个示例中停止递归的一种方法是使用 boost.mpl。例如,
#include <boost/mpl/eval_if.hpp>
#include <boost/mpl/bool.hpp>
#include <boost/mpl/integral_c.hpp>
template<int x>
struct Mine {
using type = typename boost::mpl::eval_if<boost::mpl::bool_<x == 12>,
boost::mpl::integral_c<short, 12>,
Mine<x+1>
>::type;
};
在上面的例子中,无论参数如何(只要小于或等于 12),它总是给你 12 作为值。
据我了解,您需要在 Mine 结构中中断递归。如果是这样,您可以使用此结构的完整模板特化(用于实现递归的终端情况)。看起来像这样:
template<>
struct Mine<123456>{
// It is struct represents end of recursion
};
这是使用这种方法计算阶乘的简单示例:
#include <iostream>
template <int N>
struct Factorial
{
static const int fact = N * Factorial<N - 1>::fact;
};
template <>
struct Factorial<0>
{
static const int fact = 1;
};
int main()
{
std::cout << Factorial<1>::fact << std::endl;
std::cout << Factorial<3>::fact << std::endl;
return 0;
}
这是实现 Pad
的非递归解决方案,但它也展示了如何使用偏特化来避免根据条件实例化模板:
#include <cstddef>
#include <utility>
#include <type_traits>
#include <iostream>
template<class... Ts> struct List { static constexpr std::size_t size = sizeof...(Ts); };
struct null_t { };
template<std::size_t> using make_null_t = null_t;
template<class, class> struct pad_imp2;
template<class... Ts, std::size_t... Is> struct pad_imp2<List<Ts...>, std::index_sequence<Is...>>
{
using type = List<Ts..., make_null_t<Is>...>;
};
// Don't instantiate make_index_sequence if L::size >= S.
template<class, std::size_t, bool> struct pad_imp { using type = void; };
template<class L, std::size_t S> struct pad_imp<L, S, true>
{
using type = typename pad_imp2<L, std::make_index_sequence<S - L::size>>::type;
};
template<class L, std::size_t S> using pad_hlp = typename pad_imp<L, S, L::size < S>::type;
template<class L1, class L2> struct Pad
{
using L1_padded = std::conditional_t<L1::size < L2::size, pad_hlp<L1, L2::size>, L1>;
using L2_padded = std::conditional_t<L2::size < L1::size, pad_hlp<L2, L1::size>, L2>;
};
int main()
{
using list1 = List<short, int, long>;
using list2 = List<double>;
std::cout << std::is_same<Pad<list1, list2>::L1_padded, list1>::value << '\n';
std::cout << std::is_same<Pad<list1, list2>::L2_padded, List<double, null_t, null_t>>::value << '\n';
}
填充是通过使用虚拟 make_null_t
别名模板和一组大小合适的索引生成 null_t
列表来完成的。
假设我有一个名为 List
的代理 class,它只不过是一堆 typename
s:
template<typename... items> class List {
constexpr size_t SizeOf = /*Magic code that determines the length*/;
};
假设我有另一个 class 应该带到 Lists
并输出一个版本,其中较小的版本用 null_t
s:
template<class flist,class slist>
class Pad{
typedef /*Undertermined*/ Flist;
typedef /*Undertermined*/ Slist;
};
唯一真正的问题是破坏递归....通常在模板递归中你只需专门化然后你就可以结束它。
这里有点不同,因为没有办法(至少我能看到)通过模板减速来区分两个列表之间的区别。
我尝试使用 std::conditional
来结束循环,但这不起作用。
这是一个例子:
template<int x>
class Mine{
typedef std::conditional<x == 12, Mine<x>::value, Mine<x+1>::value> value;
};
即使我有 x==12
条件,它仍然需要(或想要)清除 Mine<x+1>::value
。
那么这种情况的一般策略是什么?
在第二个示例中停止递归的一种方法是使用 boost.mpl。例如,
#include <boost/mpl/eval_if.hpp>
#include <boost/mpl/bool.hpp>
#include <boost/mpl/integral_c.hpp>
template<int x>
struct Mine {
using type = typename boost::mpl::eval_if<boost::mpl::bool_<x == 12>,
boost::mpl::integral_c<short, 12>,
Mine<x+1>
>::type;
};
在上面的例子中,无论参数如何(只要小于或等于 12),它总是给你 12 作为值。
据我了解,您需要在 Mine 结构中中断递归。如果是这样,您可以使用此结构的完整模板特化(用于实现递归的终端情况)。看起来像这样:
template<>
struct Mine<123456>{
// It is struct represents end of recursion
};
这是使用这种方法计算阶乘的简单示例:
#include <iostream>
template <int N>
struct Factorial
{
static const int fact = N * Factorial<N - 1>::fact;
};
template <>
struct Factorial<0>
{
static const int fact = 1;
};
int main()
{
std::cout << Factorial<1>::fact << std::endl;
std::cout << Factorial<3>::fact << std::endl;
return 0;
}
这是实现 Pad
的非递归解决方案,但它也展示了如何使用偏特化来避免根据条件实例化模板:
#include <cstddef>
#include <utility>
#include <type_traits>
#include <iostream>
template<class... Ts> struct List { static constexpr std::size_t size = sizeof...(Ts); };
struct null_t { };
template<std::size_t> using make_null_t = null_t;
template<class, class> struct pad_imp2;
template<class... Ts, std::size_t... Is> struct pad_imp2<List<Ts...>, std::index_sequence<Is...>>
{
using type = List<Ts..., make_null_t<Is>...>;
};
// Don't instantiate make_index_sequence if L::size >= S.
template<class, std::size_t, bool> struct pad_imp { using type = void; };
template<class L, std::size_t S> struct pad_imp<L, S, true>
{
using type = typename pad_imp2<L, std::make_index_sequence<S - L::size>>::type;
};
template<class L, std::size_t S> using pad_hlp = typename pad_imp<L, S, L::size < S>::type;
template<class L1, class L2> struct Pad
{
using L1_padded = std::conditional_t<L1::size < L2::size, pad_hlp<L1, L2::size>, L1>;
using L2_padded = std::conditional_t<L2::size < L1::size, pad_hlp<L2, L1::size>, L2>;
};
int main()
{
using list1 = List<short, int, long>;
using list2 = List<double>;
std::cout << std::is_same<Pad<list1, list2>::L1_padded, list1>::value << '\n';
std::cout << std::is_same<Pad<list1, list2>::L2_padded, List<double, null_t, null_t>>::value << '\n';
}
填充是通过使用虚拟 make_null_t
别名模板和一组大小合适的索引生成 null_t
列表来完成的。