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