Reputation: 3230
I think I just made a C++ paradox...
#include <type_traits>
#include <utility>
// If has_f trait is defined in this way, compilation breaks because of infinite recursion in template substitution
/*
template< typename T, typename Enable=void >
struct has_f : std::false_type { };
template< typename T >
struct has_f<T, decltype(f(std::declval<T>()))> : std::true_type { };
*/
// Defining has_f like this works on MSVC, gcc and CLang
namespace has_f_impl {
struct no{ };
template< typename T >
no check(...);
template< typename T >
decltype(f(std::declval<T>())) check(void const*);
template< typename T >
struct has_f : std::integral_constant<bool, !std::is_same<no, decltype(check<T>(nullptr))>::value> { };
}
using has_f_impl::has_f;
struct Foo { };
struct Bar { };
template< typename T, std::enable_if_t<std::is_same<Foo, T>::value, int> = 0 >
void f(T const&);
template< typename T, std::enable_if_t<!has_f<T const&>::value, int> = 1 >
void f(T const&);
int main() {
f(Foo()); // Calls f<Foo,0>()
f(Bar()); // Calls f<Bar,1>()
f(Foo()); // Calls f<Foo,0>()
f(Bar()); // Calls f<Bar,1>()
}
The above code surprisingly works, and in a very smart way, only using the generic f
when there's really no other option.
Also, and this is probably because of ODR, the following happens
// Includes, has_f, Foo, Bar and f as above
template< typename T, std::enable_if_t<has_f<T const&>::value>* = nullptr >
void g(T const&);
int main() {
f(Foo()); // Calls f<Foo,0>()
f(Bar()); // Calls f<Bar,1>()
f(Foo()); // Calls f<Foo,0>()
f(Bar()); // Calls f<Bar,1>()
g(Foo());
//g(Bar()); //No such function
}
As far as I tried, all of this seems to be independent from declaration order.
My question is: what's really happening here? Is this a standard-defined behavior, an undefined condition which all the compiler I tried handle in the same way, or a bug which is present by coincidence in all the compilers I tried?
Upvotes: 3
Views: 251
Reputation: 303537
I suspect this is all simply covered by [temp.inst]:
The result of an infinite recursion in instantiation is undefined.
Regardless of which way you define your has_f
, it involves infinite recursion. has_f<Bar>
involves the instantiation of f(Bar )
which involves the instantiation of has_f<Bar>
which involves the instantiation of ...
The fact that one way of defining has_f
works in some circumstances but not in others, and the other way definitely doesn't work, is just a consequence of undefined behavior. Undefined behavior is undefined.
Upvotes: 2