This question already has an answer here:
Fallback variadic constructor - why does this work?
(1 answer)
Closed 5 years ago.
Consider this program:
#include <iostream>
#include <type_traits>
using namespace std;
struct russell {
template <typename barber,
typename = typename enable_if<!is_convertible<barber, russell>::value>::type>
russell(barber) {}
};
russell verify1() { return 42L; }
russell verify2() { return 42; }
int main ()
{
verify1();
verify2();
cout << is_convertible<long, russell>::value;
cout << is_convertible<int, russell>::value;
return 0;
}
If some type barber is not convertible to russell. we attempt to create a paradox by making it convertible (enabling a converting constructor).
The output is 00 with three popular compilers, though constructors are evidently working.
I suspect the behaviour should be undefined, but cannot find anything in the standard.
What should the output of this program be, and why?
During overload resolution, template argument deduction must instantiate the default argument to obtain a complete set of template arguments to instantiate the function template with (if possible). Hence the instantiation of is_convertible<int, russell> is necessitated, which internally invokes overload resolution. The constructor template in russell is in scope in the instantiation context of the default template argument.
The crux is that is_convertible<int, russell>::value evaluates the default template argument of russell, which itself names is_convertible<int, russell>::value.
is_convertible<int, russell>::value
|
v
russell:russell(barber)
|
v
is_convertible<int, russell>::value (not in scope)
core issue 287's (unadopted) resolution seems to be the de facto rule abode by major compilers. Because the point of instantiation comes right before an entity, value's declaration is not in scope while we're evaluating its initialiser; hence our constructor has a substitution failure and is_convertible in main yields false.
Issue 287 clarifies which declarations are in scope, and which are not, namely value.
Clang and GCC do slightly differ on how they treat this situation. Take this example with a custom, transparent implementation of the trait:
#include <type_traits>
template <typename T, typename U>
struct is_convertible
{
static void g(U);
template <typename From>
static decltype(g(std::declval<From>()), std::true_type{}) f(int);
template <typename>
static std::false_type f(...);
static const bool value = decltype(f<T>()){};
};
struct russell
{
template <typename barber,
typename = std::enable_if_t<!is_convertible<barber, russell>::value>>
russell(barber) {}
};
russell foo() { return 42; }
int main() {}
Clang translates this silently. GCC complains about an infinite recursion chain: it seems to argue that value is indeed in scope in the recursive instantiation of the default argument, and so proceeds to instantiate the initializer of value again and again. However, arguably Clang is in the right, since both the current and the drafted relevant phrase in [temp.point]/4 mandate that the PoI is before the nearest enclosing declaration. I.e. that very declaration is not considered to be part of the partial instantiation (yet). Kinda makes sense if you consider the above scenario. Workaround for GCC: employ a declaration form in which the name is not declared until after the initializer is instantiated.
enum {value = decltype(f<T>()){}};
This compiles with GCC as well.
Related
I have been experimenting with a system for composable pipelines, which involves a set of 'stages', which may be templated. Each stage handles its own setup, execution and cleanup, and template deduction is used to build a minimal list of 'state' used by the pipeline. This requires quite a lot of boilerplate template code, which has shown up some apparently incongruous behaviour. Despite successful experiments, actually rolling it into our code-base resulted in errors due to invalid instantiations.
It took some time to track down the difference between the toy (working) solution, and the more rich version, but eventually it was narrowed down to an explicit namespace specification.
template<typename KeyType = bool>
struct bind_stage
{
static_assert(!std::is_same<KeyType, bool>::value, "Nope, someone default instantiated me");
};
template<typename BoundStage, typename DefaultStage>
struct test_binding {};
template<template<typename...>class StageTemplate, typename S, typename T>
struct test_binding <StageTemplate<S>, StageTemplate<T>> {};
template<typename T>
auto empty_function(T b) {}
Then our main:
int main()
{
auto binder = test_binding<bind_stage<int>, bind_stage<>>();
//empty_function(binder); // Fails to compile
::empty_function(binder); // Compiles happily
return 0;
}
Now, I'm not sure if I expect the failure, or not. On the one hand, the we create a test_binder<bind_stage<int>,bind_stage<bool>> which obviously includes the invalid instantiation bind_stage<bool> as part of its type definition. Which should fail to compile.
On the other, it's included purely as a name, not a definition. In this situation it could simply be a forward declared template and we'd expect it to work as long as nothing in the outer template actually refers to it specifically.
What I didn't expect was two different behaviours depending on whether I added a (theoretically superfluous) global namespace specifier.
I have tried this code in Visual Studio, Clang and GCC. All have the same behaviour, which makes me lean away from this being a compiler bug. Is this behaviour explained by something in the C++ standard?
EDIT:
Another example from Daniel Langr which makes less sense to me:
template <typename T>
struct X {
static_assert(sizeof(T) == 1, "Why doesn't this happen in both cases?");
};
template <typename T>
struct Y { };
template <typename T>
void f(T) { }
int main() {
auto y = Y<X<int>>{};
// f(y); // triggers static assertion
::f(y); // does not
}
Either X<int> is instantiated while defining Y<X<int>> or it is not. What does using a function in a non-specified scope have to do with anything?
Template are instantiated when needed. So why when one performs a non qualified call as f(Y<X<int>> {}); does the compiler instantiate X<int> while it does not when the call to f is qualified as in ::f(X<Y<int>>{})?
The reason is Agument-Dependent name Lookup(ADL) (see [basic.lookup.argdep]) that only takes place for non qualified calls.
In the case of the call f(Y<X<int>>{}) the compiler must look in the definition of X<int> for a declaration of a friend function:
template <typename T>
struct X {
//such function will participate to the overload resolution
//to determine which function f is called in "f(Y<X<int>>{})"
friend void f(X&){}
};
ADL involving the type of a template argument of the specialization that is the type of the function argument (ouch...) is so miss-loved (because it almost only causes bad surprises) that there is a proposal to remove it: P0934
All standard references below refers to the current ISO Standard Working Draft, generated on 2020-06-22.
[dcl.fct]/18 states that [extract, emphasis mine]:
An abbreviated function template is a function declaration that has one or more generic parameter type placeholders ([dcl.spec.auto]). An abbreviated function template is equivalent to a function template ([temp.fct]) whose template-parameter-list includes one invented type template-parameter for each generic parameter type placeholder of the function declaration, in order of appearance. [...]
Such that the following to function declarations are areuably equivalent:
template <typename T>
void f(T);
void f(auto); // re-declaration
We may note, however, that the example of [dcl.fct]/18 states that
[...]
These declarations are functionally equivalent (but not equivalent) to the following declarations.
[...]
which may arguably (I'm unsure how interpret this) conflict with the equivalence statement in the prior passage.
Now, both GCC 10.1.0 and Clang 10.0.0 (as well as GCC:HEAD and Clang:HEAD) have some mixed behavior here. If we declare a function template and later define it (/re-declare it) using a mixed classical function template syntax with abbreviated function template syntax, Clang accepts most cases (defining a previously declared function) whereas GCC rejects all (sees the (attempted) re-declarations as separately declared functions with subsequent ambiguity failures in overload resolution):
// A1: Clang OK, GCC error
template <typename T>
void a(T);
void a(auto) {}
// B1: Clang OK, GCC error
void b(auto);
template <typename T>
void b(T) {}
// C1: Clang OK, GCC error
template <typename T, typename U>
void c(T, U);
void c(auto, auto) {}
// D1: Clang OK, GCC error
template <typename T, typename U>
void d(T, U);
template <typename T>
void d(T, auto) {}
// E1: Clang error, GCC error
template <typename T>
void e(T, auto);
template <typename T>
void e(auto, T) {}
int main() {
a(0); // Clang OK, GCC error.
b(0); // Clang OK, GCC error.
c(0, '0'); // Clang OK, GCC error.
d(0, '0'); // Clang OK, GCC error.
e(0, '0'); // Clang error, GCC error.
}
Curiously, if we make the function template a class member function template, both GCC and Clang accepts cases A1 through D1, but both rejects the final case E1 above:
// A2: OK
struct Sa {
template <typename T>
void a(T);
};
void Sa::a(auto) {}
// B2: OK
struct Sb {
void b(auto);
};
template <typename T>
void Sb::b(T) {}
// C2: OK
struct Sc {
template <typename T, typename U>
void c(T, U);
};
void Sc::c(auto, auto) {}
// D2: OK
struct Sd {
template <typename T, typename U>
void d(T, U);
};
template <typename T>
void Sd::d(T, auto) {}
// E2: Error
struct Se {
template <typename T>
void e(T, auto);
};
template <typename T>
void Se::e(auto, T) {}
with the following error messages:
GCC
error: no declaration matches 'void Se::e(auto:7, T)'
note: candidate is:
'template<class T, class auto:6> void Se::e(T, auto:6)'
Clang
error: out-of-line definition of 'e' does not match
any declaration in 'Se'
Now, the name of a type template parameter is not required to be consistent over re-declaration (or a definition) of a function template, as just names a generic type placeholder.
GCC's error message is particularly interesting, hinting that invented type template parameters are treated as concrete types rather than generic type placeholders.
Question:
Which of GCC and Clang are correct regarding cases A1 through D1 (rejecting and accepting, respectively)? Are GCC and Clang correct to reject case E2 above? What standard passage (of the working draft) unambiguously supports them?
This:
template <typename T>
void e(T, auto);
Translates to this:
template<typename T, typename U>
void e(T, U);
By contrast, this:
template <typename T>
void e(auto, T) {}
translates to:
template <typename T, typename U>
void e(U, T) {}
Remember that abbreviated function template parameters are placed at the end of the template parameter list. So those aren't declaring the same template, due to reversing the order of the template parameters. The first one declares one template, the second one declares and defines a different template.
You don't get a compile error just from this because the second definition is also a declaration. However, when you use a class member, out-of-member definitions are not declarations. Therefore, they must have a matching in-member declaration. Which they don't; hence the errors.
As for the others, the "functionally equivalent (but not equivalent)" text is a non-normative notation. The actual normative text that you quoted clearly states that these are "equivalent", not merely "functionally equivalent". And since the term "equivalent", per [temp.over.link]/7, is used to match declarations and definitions, it seems to me that the standard states that the A through D cases are fine.
What's weird is that this non-normative text was introduced by the same proposal that introduced the normative text. However, the proposal that it inherited the ConceptName auto syntax from seems clear that it means "equivalent", not "functionally equivalent".
So in terms of normative text, everything seems clear. But the presence of the non-normative contradiction suggests either an editorial problem or an actual defect in the spec.
While the standard itself is clear and normatively reasonable in terms of wording, it appears that this is not what the writers of the standard intended.
P0717 introduced the concept of "functionally equivalent" as being distinct from "equivalent". And that proposal was accepted. However, P0717 was introduced early in the process of adopting the Concepts TS for C++20. In that proposal, it specifically spoke of terse template syntax, and EWG explicitly voted in favor of adopting "functionally equivalent" wording for it instead of the Concepts TS "equivalent" wording.
That is, P0717 makes it clear that the committee intended for users to be required to use consistent syntax.
However, terse template syntax from Concepts TS was removed from C++20 (or rather, never really added). Which meant that any "functionally equivalent" wording never made it in, since the feature never made it in.
Then P1141 happened, which added abbreviated template syntax, that covered much of the ground of the Concepts TS terse template syntax. But, despite one of the authors of P0717 being an author of P1141, apparently someone made a mistake in the wording and nobody caught it. This would explain why the non-normative text calls out the lack of true equivalence: because that was actually the committee's intent.
So it's very possible that this is a mistake in the normative text.
All standard references below refers to the current ISO Standard Working Draft, generated on 2020-06-22.
[dcl.fct]/18 states that [extract, emphasis mine]:
An abbreviated function template is a function declaration that has one or more generic parameter type placeholders ([dcl.spec.auto]). An abbreviated function template is equivalent to a function template ([temp.fct]) whose template-parameter-list includes one invented type template-parameter for each generic parameter type placeholder of the function declaration, in order of appearance. [...]
Such that the following to function declarations are areuably equivalent:
template <typename T>
void f(T);
void f(auto); // re-declaration
We may note, however, that the example of [dcl.fct]/18 states that
[...]
These declarations are functionally equivalent (but not equivalent) to the following declarations.
[...]
which may arguably (I'm unsure how interpret this) conflict with the equivalence statement in the prior passage.
Now, both GCC 10.1.0 and Clang 10.0.0 (as well as GCC:HEAD and Clang:HEAD) have some mixed behavior here. If we declare a function template and later define it (/re-declare it) using a mixed classical function template syntax with abbreviated function template syntax, Clang accepts most cases (defining a previously declared function) whereas GCC rejects all (sees the (attempted) re-declarations as separately declared functions with subsequent ambiguity failures in overload resolution):
// A1: Clang OK, GCC error
template <typename T>
void a(T);
void a(auto) {}
// B1: Clang OK, GCC error
void b(auto);
template <typename T>
void b(T) {}
// C1: Clang OK, GCC error
template <typename T, typename U>
void c(T, U);
void c(auto, auto) {}
// D1: Clang OK, GCC error
template <typename T, typename U>
void d(T, U);
template <typename T>
void d(T, auto) {}
// E1: Clang error, GCC error
template <typename T>
void e(T, auto);
template <typename T>
void e(auto, T) {}
int main() {
a(0); // Clang OK, GCC error.
b(0); // Clang OK, GCC error.
c(0, '0'); // Clang OK, GCC error.
d(0, '0'); // Clang OK, GCC error.
e(0, '0'); // Clang error, GCC error.
}
Curiously, if we make the function template a class member function template, both GCC and Clang accepts cases A1 through D1, but both rejects the final case E1 above:
// A2: OK
struct Sa {
template <typename T>
void a(T);
};
void Sa::a(auto) {}
// B2: OK
struct Sb {
void b(auto);
};
template <typename T>
void Sb::b(T) {}
// C2: OK
struct Sc {
template <typename T, typename U>
void c(T, U);
};
void Sc::c(auto, auto) {}
// D2: OK
struct Sd {
template <typename T, typename U>
void d(T, U);
};
template <typename T>
void Sd::d(T, auto) {}
// E2: Error
struct Se {
template <typename T>
void e(T, auto);
};
template <typename T>
void Se::e(auto, T) {}
with the following error messages:
GCC
error: no declaration matches 'void Se::e(auto:7, T)'
note: candidate is:
'template<class T, class auto:6> void Se::e(T, auto:6)'
Clang
error: out-of-line definition of 'e' does not match
any declaration in 'Se'
Now, the name of a type template parameter is not required to be consistent over re-declaration (or a definition) of a function template, as just names a generic type placeholder.
GCC's error message is particularly interesting, hinting that invented type template parameters are treated as concrete types rather than generic type placeholders.
Question:
Which of GCC and Clang are correct regarding cases A1 through D1 (rejecting and accepting, respectively)? Are GCC and Clang correct to reject case E2 above? What standard passage (of the working draft) unambiguously supports them?
This:
template <typename T>
void e(T, auto);
Translates to this:
template<typename T, typename U>
void e(T, U);
By contrast, this:
template <typename T>
void e(auto, T) {}
translates to:
template <typename T, typename U>
void e(U, T) {}
Remember that abbreviated function template parameters are placed at the end of the template parameter list. So those aren't declaring the same template, due to reversing the order of the template parameters. The first one declares one template, the second one declares and defines a different template.
You don't get a compile error just from this because the second definition is also a declaration. However, when you use a class member, out-of-member definitions are not declarations. Therefore, they must have a matching in-member declaration. Which they don't; hence the errors.
As for the others, the "functionally equivalent (but not equivalent)" text is a non-normative notation. The actual normative text that you quoted clearly states that these are "equivalent", not merely "functionally equivalent". And since the term "equivalent", per [temp.over.link]/7, is used to match declarations and definitions, it seems to me that the standard states that the A through D cases are fine.
What's weird is that this non-normative text was introduced by the same proposal that introduced the normative text. However, the proposal that it inherited the ConceptName auto syntax from seems clear that it means "equivalent", not "functionally equivalent".
So in terms of normative text, everything seems clear. But the presence of the non-normative contradiction suggests either an editorial problem or an actual defect in the spec.
While the standard itself is clear and normatively reasonable in terms of wording, it appears that this is not what the writers of the standard intended.
P0717 introduced the concept of "functionally equivalent" as being distinct from "equivalent". And that proposal was accepted. However, P0717 was introduced early in the process of adopting the Concepts TS for C++20. In that proposal, it specifically spoke of terse template syntax, and EWG explicitly voted in favor of adopting "functionally equivalent" wording for it instead of the Concepts TS "equivalent" wording.
That is, P0717 makes it clear that the committee intended for users to be required to use consistent syntax.
However, terse template syntax from Concepts TS was removed from C++20 (or rather, never really added). Which meant that any "functionally equivalent" wording never made it in, since the feature never made it in.
Then P1141 happened, which added abbreviated template syntax, that covered much of the ground of the Concepts TS terse template syntax. But, despite one of the authors of P0717 being an author of P1141, apparently someone made a mistake in the wording and nobody caught it. This would explain why the non-normative text calls out the lack of true equivalence: because that was actually the committee's intent.
So it's very possible that this is a mistake in the normative text.
I wasted countless hours to pinpoint an issue with gcc. I wanted to test our code base with another compiler to look for more warnings that Clang might have missed. I was shocked that practically half of the project stopped to compile due to failure of template argument deduction. Here I've tried to dumb my case down to the simplest piece of code.
#include <type_traits>
struct Foo
{ };
// This is a template function declaration, where second template argument declared without a default
template <typename T, typename>
void foo(const Foo & foo, T t);
// This is a template function definition; second template argument now has a default declared
template <typename T, typename = typename std::enable_if<1>::type>
void foo(const Foo & foo, T t)
{
}
int main(int argc, char ** argv)
{
foo(Foo{}, 1);
return 0;
}
Ignore a 1 in the std::enable_if<1>. Obviously it's a constant value just to not complicate things when it does not matter.
This piece of code compiles[1] with clang (3.4 through 4.0), icc (16, 17), Visual C++ (19.00.23506). Basically, I couldn't find any other c++11 compiler that, except gcc (4.8 through 7.1), does not compile this piece of code.
The question is, who's right and who's wrong here? Does gcc behave according to the standard?
Obviously this is not a critical issue. I can easily move std::enable_if to the declaration. The only victim would be aesthetics. But it is nice to be able to hide an ugly 100 characters long std::enable_if piece of code, that is not immediately relevant for the user of the library function, in the implementation.
Live example on godbolt.org.
What the standard says ([1] page 350):
The set of default template-arguments available for use with a
template declaration or definition is obtained by merging the default
arguments from the definition (if in scope) and all declarations in
scope in the same way default function arguments are (8.3.6). [
Example:
template<class T1, class T2 = int> class A;
template<class T1 = int, class T2> class A;
is equivalent to
template<class T1 = int, class T2 = int> class A;
— end example ]
So GCC is wrong here. It ignores default template arguments in declarations.
Not all declarations, only function template declarations. Class template declarations are okay:
#include <type_traits>
template <typename T, typename>
struct Foo;
template <typename T, typename = typename std::enable_if<1>::type>
struct Foo
{
T t;
};
int main()
{
Foo<int> foo;
return 0;
}
Live example on godbolt.org
Probably it is due to the nature of how non-default arguments are deduced. In the function template they are deducted from function arguments. In the class template we have to specify them explicitly.
Anyway, I have created a bug report.
I am trying to understand why a piece of template metaprogramming is not generating an infinite recursion. I tried to reduce the test case as much as possible, but there's still a bit of setup involved, so bear with me :)
The setup is the following. I have a generic function foo(T) which delegates the implementation to a generic functor called foo_impl via its call operator, like this:
template <typename T, typename = void>
struct foo_impl {};
template <typename T>
inline auto foo(T x) -> decltype(foo_impl<T>{}(x))
{
return foo_impl<T>{}(x);
}
foo() uses decltype trailing return type for SFINAE purposes. The default implementation of foo_impl does not define any call operator. Next, I have a type-trait that detects whether foo() can be called with an argument of type T:
template <typename T>
struct has_foo
{
struct yes {};
struct no {};
template <typename T1>
static auto test(T1 x) -> decltype(foo(x),void(),yes{});
static no test(...);
static const bool value = std::is_same<yes,decltype(test(std::declval<T>()))>::value;
};
This is just the classic implementation of a type trait via expression SFINAE:
has_foo<T>::value will be true if a valid foo_impl specialisation exists for T, false otherwise. Finally, I have two specialisations of the the implementation functor for integral types and for floating-point types:
template <typename T>
struct foo_impl<T,typename std::enable_if<std::is_integral<T>::value>::type>
{
void operator()(T) {}
};
template <typename T>
struct foo_impl<T,typename std::enable_if<has_foo<unsigned>::value && std::is_floating_point<T>::value>::type>
{
void operator()(T) {}
};
In the last foo_impl specialisation, the one for floating-point types, I have added the extra condition that foo() must be available for the type unsigned (has_foo<unsigned>::value).
What I don't understand is why the compilers (GCC & clang both) accept the following code:
int main()
{
foo(1.23);
}
In my understanding, when foo(1.23) is called the following should happen:
the specialisation of foo_impl for integral types is discarded because 1.23 is not integral, so only the second specialisation of foo_impl is considered;
the enabling condition for the second specialisation of foo_impl contains has_foo<unsigned>::value, that is, the compiler needs to check if foo() can be called on type unsigned;
in order to check if foo() can be called on type unsigned, the compiler needs again to select a specialisation of foo_impl among the two available;
at this point, in the enabling condition for the second specialisation of foo_impl the compiler encounters again the condition has_foo<unsigned>::value.
GOTO 3.
However, it seems like the code is happily accepted both by GCC 5.4 and Clang 3.8. See here: http://ideone.com/XClvYT
I would like to understand what is going on here. Am I misunderstanding something and the recursion is blocked by some other effect? Or maybe am I triggering some sort of undefined/implementation defined behaviour?
has_foo<unsigned>::value is a non-dependent expression, so it immediately triggers instantiation of has_foo<unsigned> (even if the corresponding specialization is never used).
The relevant rules are [temp.point]/1:
For a function template specialization, a member function template specialization, or a specialization for a member function or static data member of a class template, if the specialization is implicitly instantiated because it is referenced from within another template specialization and the context from which it is referenced depends on a template parameter, the point of instantiation of the specialization is the point of instantiation of the enclosing specialization. Otherwise, the point of instantiation for such a specialization immediately follows the namespace scope declaration or definition that refers to the specialization.
(note that we're in the non-dependent case here), and [temp.res]/8:
The program is
ill-formed, no diagnostic required, if:
- [...]
- a hypothetical instantiation of a template immediately following its definition would be ill-formed due to a construct that does not depend on a template parameter, or
- the interpretation of such a construct in the hypothetical instantiation is different from the interpretation of the corresponding construct in any actual instantiation of the template.
These rules are intended to give the implementation freedom to instantiate has_foo<unsigned> at the point where it appears in the above example, and to give it the same semantics as if it had been instantiated there. (Note that the rules here are actually subtly wrong: the point of instantiation for an entity referenced by the declaration of another entity actually must immediately precede that entity rather than immediately following it. This has been reported as a core issue, but it's not on the issues list yet as the list hasn't been updated for a while.)
As a consequence, the point of instantiation of has_foo within the floating-point partial specialization occurs before the point of declaration of that specialization, which is after the > of the partial specialization per [basic.scope.pdecl]/3:
The point of declaration for a class or class template first declared by a class-specifier is immediately after the identifier or simple-template-id (if any) in its class-head (Clause 9).
Therefore, when the call to foo from has_foo<unsigned> looks up the partial specializatios of foo_impl, it does not find the floating-point specialization at all.
A couple of other notes about your example:
1) Use of cast-to-void in comma operator:
static auto test(T1 x) -> decltype(foo(x),void(),yes{});
This is a bad pattern. operator, lookup is still performed for a comma operator where one of its operands is of class or enumeration type (even though it can never succeed). This can result in ADL being performed [implementations are permitted but not required to skip this], which triggers the instantiation of all associated classes of the return type of foo (in particular, if foo returns unique_ptr<X<T>>, this can trigger the instantiation of X<T> and may render the program ill-formed if that instantiation doesn't work from this translation unit). You should prefer to cast all operands of a comma operator of user-defined type to void:
static auto test(T1 x) -> decltype(void(foo(x)),yes{});
2) SFINAE idiom:
template <typename T1>
static auto test(T1 x) -> decltype(void(foo(x)),yes{});
static no test(...);
static const bool value = std::is_same<yes,decltype(test(std::declval<T>()))>::value;
This is not a correct SFINAE pattern in the general case. There are a few problems here:
if T is a type that cannot be passed as an argument, such as void, you trigger a hard error instead of value evaluating to false as intended
if T is a type to which a reference cannot be formed, you again trigger a hard error
you check whether foo can be applied to an lvalue of type remove_reference<T> even if T is an rvalue reference
A better solution is to put the entire check into the yes version of test instead of splitting the declval portion into value:
template <typename T1>
static auto test(int) -> decltype(void(foo(std::declval<T1>())),yes{});
template <typename>
static no test(...);
static const bool value = std::is_same<yes,decltype(test<T>(0))>::value;
This approach also more naturally extends to a ranked set of options:
// elsewhere
template<int N> struct rank : rank<N-1> {};
template<> struct rank<0> {};
template <typename T1>
static no test(rank<2>, std::enable_if_t<std::is_same<T1, double>::value>* = nullptr);
template <typename T1>
static yes test(rank<1>, decltype(foo(std::declval<T1>()))* = nullptr);
template <typename T1>
static no test(rank<0>);
static const bool value = std::is_same<yes,decltype(test<T>(rank<2>()))>::value;
Finally, your type trait will evaluate faster and use less memory at compile time if you move the above declarations of test outside the definition of has_foo (perhaps into some helper class or namespace); that way, they do not need to be redundantly instantiated once for each use of has_foo.
It's not actually UB. But it really shows you how TMP is complex...
The reason this doesn't infinitely recurse is because of completeness.
template <typename T>
struct foo_impl<T,typename std::enable_if<std::is_integral<T>::value>::type>
{
void operator()(T) {}
};
// has_foo here
template <typename T>
struct foo_impl<T,typename std::enable_if<has_foo<unsigned>::value && std::is_floating_point<T>::value>::type>
{
void operator()(T) {}
};
When you call foo(3.14);, you instantiate has_foo<float>. That in turn SFINAEs on foo_impl.
The first one is enabled if is_integral. Obviously, this fails.
The second foo_impl<float> is now considered. Trying to instantiate it, the compiles sees has_foo<unsigned>::value.
Back to instantiating foo_impl: foo_impl<unsigned>!
The first foo_impl<unsigned> is a match.
The second one is considered. The enable_if contains has_foo<unsigned> - the one the compiler is already trying to instantiate.
Since it's currently being instantiated, it's incomplete, and this specialization is not considered.
Recursion stops, has_foo<unsigned>::value is true, and your code snippet works!
So, you want to know how it comes down to it in the standard? Okay.
[14.7.1/1] If a class template has been declared, but not defined, at the point of instantiation ([temp.point]), the instantiation yields an incomplete class type.
(incomplete)