简体   繁体   中英

Why does decltype return type fail for recursive template, while return type deduction works just fine?

While working on a C++11 type-set, I tried to implement this function (stripped down to the minimum):

constexpr auto test() -> bool;

template <typename T, typename... Rest>
constexpr auto test() -> decltype(test<Rest...>())
{
  return {};
}

Both gcc and clang choke on this. Clang says:

test.cpp:54:40: error: 'Rest' does not refer to a value
constexpr auto test() -> decltype(test<Rest...>())
                                  ^

gcc complains:

test.cpp:54:44: error: expected primary-expression before ‘...’ token
 constexpr auto test() -> decltype(test<Rest...>())

I guess this is because the variadic version of test is not even fully declared when the decltype looks at it.

However, when I use return type deduction in C++14, this compiles just fine:

constexpr auto test() -> bool;

template <typename T, typename... Rest>
constexpr auto test()
{
  return test<Rest...>();
}

Seems like test is considered sufficiently declared here.

I wonder why this doesn't work for the decltype variant? Even if I turn on C++14 support?

PS: It turns out, that I cannot really call even the C++14 function, so maybe the whole thing is botched...

One problem is that your first test overload is not a function template, so can't be called with test<>() syntax.

However, decltype doesn't really work with recursive variadic functions like that, since the return type is part of the declaration of the function, so that overload is not declared when the name is looked up.

You can get around this in C++11 by using template classes instead:

template <typename... Ts>
struct test;

template <typename T, typename... Ts>
struct test<T,Ts...> {
    static decltype(test<Ts...>::value()) value() { return test<Ts...>::value(); }   
};

template <>
struct test<> {
    static bool value() { return true; }
};

Live demo


In C++14 you can make the first overload take a single template parameter and the second take two and a parameter pack:

template <typename T>
constexpr auto test() -> bool { return true; }

template <typename T, typename U, typename... Rest>
constexpr auto test()
{
  return test<U,Rest...>();
}

Live demo

While the template function itself is being declared, the template function itself has not been declared. So it is not visible to the trailing return type decltype.

You can fix this with ADL. If your template function takes an argument from the same namespace as your template function, the lookup for the return type becomes willing to look at the template function itself. This is because templates do lookup for their return type signature using both the context just before they where declared, and using ADL on each of the parameters.

template<class...Ts> struct types {};

namespace bob{
  struct mytag{};
  constexpr auto test(mytag, types<>) -> bool{ return true; }

  template <typename T, typename... Rest>
  constexpr auto test(mytag, types<T,Rest...>)
  -> decltype( test( mytag{}, types<Rest...>{} ) )
  {
    return test(mytag{},types<Rest...>{});
  }
}
template<class...Ts>
constexpr auto test()
->decltype( bob::test( bob::mytag{}, types<Ts...>{} ) ){
  return bob::test( bob::mytag{}, types<Ts...>{} );
}

You may need constexpr types(){}; and similar for mytag depending on compiler.

This also fixes the fact that test<> is illegal in your original code. Functions do much better with types passed by (tag template wrapped) value in my experience. Overloading is more friendly.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM