Home > OS >  Recursive Template Instantiation failed with tuples
Recursive Template Instantiation failed with tuples

Time:10-20

I have the following code that iterates through the types of a std::tuple and concatenates their names as strings.

#include <type_traits>
#include <tuple>
#include <string>

template<typename types_T, int n, typename T>
concept tuple_element_is = (std::is_same<typename std::tuple_element<n, types_T>::type, T>::value); 

template<typename types_T, int n>
    requires tuple_element_is<types_T, n, float>
constexpr const std::string foo() {
    if constexpr (n < std::tuple_size<types_T>::value - 1) {
        return "float"   foo<types_T, n   1>();
    } else {
        return "float";
    }    
}
template<typename types_T, int n>
    requires tuple_element_is<types_T, n, int>
constexpr const std::string foo() {
    if constexpr (n < std::tuple_size<types_T>::value - 1) {
        return "int"   foo<types_T, n   1>();
    } else {
        return "int";
    }
}

auto t0 = foo<std::tuple<int>, 0>();
auto t1 = foo<std::tuple<float>, 0>();

auto t2 = foo<std::tuple<int, int>, 0>();
auto t3 = foo<std::tuple<int, float>, 0>();
//auto t4 = foo<std::tuple<float, int>, 0>(); -- does not compile
auto t5 = foo<std::tuple<float, float>, 0>();

auto t6 = foo<std::tuple<int, int, int>, 0>();
auto t7 = foo<std::tuple<int, int, float>, 0>();
//auto t8 = foo<std::tuple<int, float, int>, 0>(); -- does not compile
auto t9 = foo<std::tuple<int, float, float>, 0>();
//auto t10 = foo<std::tuple<float, int, int>, 0>(); -- does not compile
//auto t11 = foo<std::tuple<float, int, float>, 0>(); -- does not compile
//auto t12 = foo<std::tuple<float, float, int>, 0>(); -- does not compile
auto t13 = foo<std::tuple<float, float, float>, 0>();

The instantiation t4, t8, t10, t11, t12 do not compile and produced this error:

note:   template argument deduction/substitution failed:
note: constraints not satisfied
In substitution of ‘template<class types_T, int n>  requires  tuple_element_is<types_T, n, float> constexpr const string foo() [with types_T = std::tuple<float, int>; int n = 1]’:
   required from ‘constexpr const string foo() [with types_T = std::tuple<float, int>; int n = 0; std::string = std::__cxx11::basic_string<char>]’
   required from here
   required for the satisfaction of ‘tuple_element_is<types_T, n, float>’ [with types_T = std::tuple<float, int>; n = 1]
 note: the expression ‘std::is_same<typename std::tuple_element<(long unsigned int)n, types_T>::type, T>::value [with n = 1; types_T = std::tuple<float, int>; T = float]’ evaluated to ‘false’

The last note is interesting, because it is true that this expression evaluates to false, but he used the float variant for the second call. Maybe I'm using it wrong, but the compiler should re-check the requirements for each recursive call of foo and select the correct substitution, in this case int. More interestingly, it works the other way around for t3, t7 Note that calling foo<std::tuple<float, int>, 1>(); directly also works.

I have tested it with both GCC and clang and they produce the same result.

Any idea?

CodePudding user response:

Clang gives me a very straightforward error message:

error: call to function 'foo' that is neither visible in the template definition nor found by argument-dependent lookup
        return "float"   foo<types_T, n   1>();
                         ^
note: in instantiation of function template specialization 'foo<std::tuple<float, int>, 0>' requested here
auto t4 = foo<std::tuple<float, int>, 0>(); //-- does not compile
          ^
note: 'foo' should be declared prior to the call site
constexpr const std::string foo() {

The int variant of foo is not visible in the float variant, and "should be declared prior to the call site". In other words, it should be forward-declared, like this:

template<typename types_T, int n>
    requires tuple_element_is<types_T, n, int>
constexpr const std::string foo();

Demo

GCC's error message is a little harder to understand, but it's still relatively straightforward when you're used to template related error messages:

error: no matching function for call to 'foo<std::tuple<float, int>, (0   1)>()'
   12 |         return "float"   foo<types_T, n   1>();
      |                          ~~~~~~~~~~~~~~~~~~~^~
note: candidate: 'template<class types_T, int n>  requires  tuple_element_is<types_T, n, float> constexpr const std::string foo()'
   10 | constexpr const std::string foo() {

It doesn't mention any other candidate, meaning that it only considers the one it can see: the first one. Again, because the second one is not declared by that point.

  • Related