【问题标题】:Why does this exceed the maximum recursive template depth?为什么这会超过最大递归模板深度?
【发布时间】:2014-06-15 23:57:48
【问题描述】:

我一直在使用可变参数模板并注意到以下内容。

这很好用:

auto t = std::make_tuple(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16);

这将给出错误(gcc 4.8.2(编辑:Clang 3.4)默认最大深度为 256):

auto t2 = std::make_tuple(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17);

但是,直接创建元组是可行的:

std::tuple<int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int> t3(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17);

我在尝试创建返回模板类的模板函数时注意到了这一点。

template <typename...Arguments>
struct Testing {
  std::tuple<Arguments...> t;
  Testing(Arguments...args) : t(args...) {}
};

template <typename... Arguments>
Testing<Arguments...> create(Arguments... args) {
  return Testing<Arguments...>(args...);
}

在这种情况下,这将起作用:

auto t4 = create(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16);

这不会:

auto t5 = create(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17);

【问题讨论】:

  • 可能是因为create是一个“递归模板”,当你给它17个参数时它需要17个深度,当你给它16个参数时它需要16个深度,并且限制是(显然)16。
  • g++4.8.2 需要一个正好为 231 的实例化深度来编译带有 17 个参数的 make_tupleLive example make_tuple 本身只增加了一个实例化深度,据我所知,问题可能是 tuple 本身。
  • 噢噢噢,这解释了我的困惑
  • 他们绝对应该努力让这个模板超魔的一些递归性降低
  • @user3586046 noexcept 检查用于移动构造函数,所以trying to move that tuple fails,甚至for much fewer arguments

标签: c++ templates c++11 variadic-templates


【解决方案1】:

问题不是make_tuple,而是libstdc++(gcc4.8.2)中tuple的move构造函数。

对于类模板,成员函数仅在使用时才被实例化。 noexcept 规范也有类似的延迟,参见例如CWG issue 1330.

当从make_tuple 初始化一个变量时,移动构造函数被实例化,即使它被省略(例如,检查它是否格式错误)。这就是为什么您会看到只定义tuple 变量使用make_tuple 之间的区别。

移动构造函数有一个条件noexcept,它是递归实现的。因此,对于每个模板参数,需要恒定数量的附加实例化。超过最大实例化深度时clang++的错误输出摘录:(振作起来,输入的文字墙)

/usr/lib/gcc/x86_64-linux-gnu/4.8/../../../../include/c++/4.8/type_traits:803:24:注意:在'__test的默认参数的实例化中,此处需要 std::_Tuple_impl &&>' 静态 true_type __test(int); ^~~~~~~~~~~ /usr/lib/gcc/x86_64-linux-gnu/4.8/../../../../include/c++/4.8/type_traits:803:24:注意:将推导的模板参数替换为函数模板' __test' [with _Tp = std::_Tuple_impl, _Arg = std::_Tuple_impl &&, $2 = ] 静态 true_type __test(int); ^ /usr/lib/gcc/x86_64-linux-gnu/4.8/../../../../include/c++/4.8/type_traits:117:14: 注意:在模板类 'std:: 的实例化中__is_direct_constructible_impl, std::_Tuple_impl &&>' 在这里请求 : 公共条件::type ^ /usr/lib/gcc/x86_64-linux-gnu/4.8/../../../../include/c++/4.8/type_traits:818:14: 注意:在模板类的实例化中'std:: __and_ >, std::__is_direct_constructible_impl, std::_Tuple_impl &&> >' 在这里请求 : 公共__and_, ^ /usr/lib/gcc/x86_64-linux-gnu/4.8/../../../../include/c++/4.8/type_traits:896:14: 注意:在模板类的实例化中'std:: __is_direct_constructible_new_safe, std::_Tuple_impl &&>' 在这里请求 : 公共条件::值, ^ /usr/lib/gcc/x86_64-linux-gnu/4.8/../../../../include/c++/4.8/type_traits:904:39: 注意:在模板类的实例化中'std:: __is_direct_constructible_new, std::_Tuple_impl &&>' 在这里请求 : public integral_constant, std::_Tuple_impl &&>' 在这里请求 : 公共 __is_direct_constructible ^ /usr/lib/gcc/x86_64-linux-gnu/4.8/../../../../include/c++/4.8/type_traits:956:39: 注意:在模板类的实例化中'std:: __is_constructible_impl, std::_Tuple_impl &&>' 在这里请求 : public integral_constant, std::_Tuple_impl &&>' 在这里请求 : 公共条件::type ^ /usr/lib/gcc/x86_64-linux-gnu/4.8/../../../../include/c++/4.8/type_traits:1042:14: 注意:在模板类 'std:: 的实例化中__and_, std::_Tuple_impl &&>, std::__is_nt_constructible_impl, std::_Tuple_impl &&> >' 在这里请求 : 公共__and_, ^ /usr/lib/gcc/x86_64-linux-gnu/4.8/../../../../include/c++/4.8/type_traits:1073:14: 注意:在模板类的实例化中'std:: is_nothrow_constructible, std::_Tuple_impl &&>' 在这里请求 : 公共 is_nothrow_constructible ^ /usr/lib/gcc/x86_64-linux-gnu/4.8/../../../../include/c++/4.8/type_traits:1079:14: 注意:在模板类的实例化中'std:: __is_nothrow_move_constructible_impl, false>' 在这里请求 : public __is_nothrow_move_constructible_impl ^ /usr/lib/gcc/x86_64-linux-gnu/4.8/../../../../include/c++/4.8/type_traits:117:14: 注意:在模板类 'std:: 的实例化中is_nothrow_move_constructible >' 在这里请求 : 公共条件::type ^ /usr/lib/gcc/x86_64-linux-gnu/4.8/../../../../include/c++/4.8/tuple:268:16: 注意:在模板类的实例化中'std:: __and_, std::is_nothrow_move_constructible >>' 在这里请求 没有例外(__and_, ^ /usr/lib/gcc/x86_64-linux-gnu/4.8/../../../../include/c++/4.8/type_traits:802:24:注意:在“_Tuple_impl”的异常规范的实例化中在这里请求 = decltype(::new _Tp(declval()))>

我们可以在这里看到实现,例如is_nothrow_move_constructibleis_nothrow_constructible 形式实现,以 __is_nt_constructible 等形式实现,用于 15 个实例化级别。这就像调用堆栈一样打印,因此您可以从底部开始跟踪实例化。


这意味着tuple 的每个模板参数都需要 15 个额外的实例化级别才能进行此检查。最重要的是,始终需要 9 个级别(恒定深度)。

因此,17 个参数需要 17*15+9 == 264 的实例化深度。

【讨论】:

  • C++11 建议最大实例化深度至少为 1024,g++ 默认最大深度为 900。请参阅 g++4.8.2 manual 中的 -ftemplate-depth 条目。
  • 有没有办法绕过这个?我的意思是可以让this 在不增加实例化深度的情况下进行编译吗?
  • 或者甚至可能是一个不同的元组实现确实遇到了这些问题? (我检查了 boost::tuple 也失败了)
  • @LorahAttkins 应该可以通过不同地实施检查(如内联)来减少每个元组元素的模板实例化数量,也许可以应用技术来减少实例化深度 i> (同时保持实例化的数量不变甚至增加它们,即展平实例化)。这两种方法都需要使用不同的std::tuple 实现。对于 libstdc++ 实现,它可能是短路的,因此您可以通过提供非 noexcept-move-constructible 元组元素类型来避免实例化。
猜你喜欢
  • 1970-01-01
  • 2014-05-13
  • 1970-01-01
  • 2017-11-05
  • 2013-11-30
  • 2020-12-13
  • 2013-03-03
  • 2016-07-09
  • 2015-08-14
相关资源
最近更新 更多