Вопрос

I've been playing around with variadic templates and noticed the following.

This works fine:

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

This will give the error (gcc 4.8.2 (edit: Clang 3.4) has maximum depth of 256 by default):

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

However, creating the tuple directly will work:

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);

I noticed this while trying to create a templated function that returns a templated class.

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...);
}

In this case, this will work:

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

and this won't:

auto t5 = create(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17);
Это было полезно?

Решение

The problem is not make_tuple, but the move constructor of tuple in libstdc++ (gcc4.8.2).

For class templates, the member functions are only instantiated when used. The noexcept-specification is delayed similarly, see e.g. CWG issue 1330.

When initialising a variable from make_tuple, the move constructor is instantiated, even if it is elided (e.g. to check if it is ill-formed). That's why you see a difference between just defining a tuple variable and using make_tuple.

The move constructor has a conditional noexcept that is implemented recursively. Therefore, for each template argument, a constant number of additional instantiations is required. An excerpt of clang++'s error output when exceeding the max instantiation depth: (brace yourself, wall of text incoming)

/usr/lib/gcc/x86_64-linux-gnu/4.8/../../../../include/c++/4.8/type_traits:803:24: note: in instantiation of default argument for '__test, std::_Tuple_impl &&>' required here
      static true_type __test(int);
                       ^~~~~~~~~~~
/usr/lib/gcc/x86_64-linux-gnu/4.8/../../../../include/c++/4.8/type_traits:803:24: note: while substituting deduced template arguments into function template '__test' [with _Tp = std::_Tuple_impl, _Arg = std::_Tuple_impl &&, $2 = ]
      static true_type __test(int);
                       ^
/usr/lib/gcc/x86_64-linux-gnu/4.8/../../../../include/c++/4.8/type_traits:117:14: note: in instantiation of template class 'std::__is_direct_constructible_impl, std::_Tuple_impl &&>' requested here
    : public conditional::type
             ^
/usr/lib/gcc/x86_64-linux-gnu/4.8/../../../../include/c++/4.8/type_traits:818:14: note: in instantiation of template class 'std::__and_ >, std::__is_direct_constructible_impl, std::_Tuple_impl &&> >' requested here
    : public __and_,
             ^
/usr/lib/gcc/x86_64-linux-gnu/4.8/../../../../include/c++/4.8/type_traits:896:14: note: in instantiation of template class 'std::__is_direct_constructible_new_safe, std::_Tuple_impl &&>' requested here
    : public conditional::value,
             ^
/usr/lib/gcc/x86_64-linux-gnu/4.8/../../../../include/c++/4.8/type_traits:904:39: note: in instantiation of template class 'std::__is_direct_constructible_new, std::_Tuple_impl &&>' requested here
    : public integral_constant, std::_Tuple_impl &&>' requested here
    : public __is_direct_constructible
             ^
/usr/lib/gcc/x86_64-linux-gnu/4.8/../../../../include/c++/4.8/type_traits:956:39: note: in instantiation of template class 'std::__is_constructible_impl, std::_Tuple_impl &&>' requested here
    : public integral_constant, std::_Tuple_impl &&>' requested here
    : public conditional::type
                         ^
/usr/lib/gcc/x86_64-linux-gnu/4.8/../../../../include/c++/4.8/type_traits:1042:14: note: in instantiation of template class 'std::__and_, std::_Tuple_impl &&>, std::__is_nt_constructible_impl, std::_Tuple_impl &&> >' requested here
    : public __and_,
             ^
/usr/lib/gcc/x86_64-linux-gnu/4.8/../../../../include/c++/4.8/type_traits:1073:14: note: in instantiation of template class 'std::is_nothrow_constructible, std::_Tuple_impl &&>' requested here
    : public is_nothrow_constructible
             ^
/usr/lib/gcc/x86_64-linux-gnu/4.8/../../../../include/c++/4.8/type_traits:1079:14: note: in instantiation of template class 'std::__is_nothrow_move_constructible_impl, false>' requested here
    : public __is_nothrow_move_constructible_impl
             ^
/usr/lib/gcc/x86_64-linux-gnu/4.8/../../../../include/c++/4.8/type_traits:117:14: note: in instantiation of template class 'std::is_nothrow_move_constructible >' requested here
    : public conditional::type
             ^
/usr/lib/gcc/x86_64-linux-gnu/4.8/../../../../include/c++/4.8/tuple:268:16: note: in instantiation of template class 'std::__and_, std::is_nothrow_move_constructible > >' requested here
      noexcept(__and_,
               ^
/usr/lib/gcc/x86_64-linux-gnu/4.8/../../../../include/c++/4.8/type_traits:802:24: note: in instantiation of exception specification for '_Tuple_impl' requested here
             = decltype(::new _Tp(declval()))>

We can see here the implementation e.g. of is_nothrow_move_constructible in terms of is_nothrow_constructible which is implemented in terms of __is_nt_constructible and so on, for 15 instantiation levels. This is printed like a call stack, so you can follow the instantiations by starting from the bottom.


This means that each template argument for tuple requires 15 additional instantiation levels for this check. On top of that, 9 levels are always required (constant depth).

Therefore, 17 arguments require an instantiation depth of 17*15+9 == 264.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top