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.