Self introduction
Hello everyone, I am an innocent compiler.
The first call
test(a, b); // works
In this call, the argument type is A
. Let me first consider the first overload:
template <class T>
void test(T, T);
Easy. T = A
.
Now consider the second:
template <class T>
void test(Wrapper<T>, Wrapper<T>);
Hmm ... what? Wrapper<T>
for A
? I have to instantiate Wrapper<T>
for every possible type T
in the world just to make sure that a parameter of type Wrapper<T>
, which might be specialized, can't be initialized with an argument of type A
? Well ... I don't think I'm going to do that ...
Hence I will not instantiate any Wrapper<T>
. I will choose the first overload.
The second call
test<A>(a, b); // doesn't work
test<A>
? Aha, I don't have to do deduction. Let me just check the two overloads.
template <class T>
void test(T, T);
T = A
. Now substitute — the signature is (A, A)
. Perfect.
template <class T>
void test(Wrapper<T>, Wrapper<T>);
T = A
. Now subst ... Wait, I never instantiated Wrapper<A>
? I can't substitute then. How can I know whether this would be a viable overload for the call? Well, I have to instantiate it first. (instantiating) Wait ...
using type = typename T::type;
A::type
? Error!
Back to L. F.
Hello everyone, I am L. F. Let's review what the compiler has done.
Was the compiler innocent enough? Did he (she?) conform to the standard?
@YSC has pointed out that [temp.over]/1 says:
When a call to the name of a function or function template is written
(explicitly, or implicitly using the operator notation), template
argument deduction ([temp.deduct]) and checking of any explicit
template arguments ([temp.arg]) are performed for each function
template to find the template argument values (if any) that can be
used with that function template to instantiate a function template
specialization that can be invoked with the call arguments. For each
function template, if the argument deduction and checking succeeds,
the template-arguments (deduced and/or explicit) are used to
synthesize the declaration of a single function template
specialization which is added to the candidate functions set to be
used in overload resolution. If, for a given function template,
argument deduction fails or the synthesized function template
specialization would be ill-formed, no such function is added to the
set of candidate functions for that template. The complete set of
candidate functions includes all the synthesized declarations and all
of the non-template overloaded functions of the same name. The
synthesized declarations are treated like any other functions in the
remainder of overload resolution, except as explicitly noted in
[over.match.best].
The missing type
leads to a hard error. Read https://stackoverflow.com/a/15261234. Basically, we have two stages when determining whether template<class T> void test(Wrapper<T>, Wrapper<T>)
is the desired overload:
Instantiation. In this case, we (fully) instantiate Wrapper<A>
. In this stage, using type = typename T::type;
is problematic because A::type
is nonexistent. Problems that occur in this stage are hard errors.
Substitution. Since the first stage already fails, this stage is not even reached in this case. Problems that occur in this stage are subject to SFINAE.
So yeah, the innocent compiler has done the right thing.
test(a, b);
,T
cannot be deduced for the second overload oftest
, so this overload is discarded before the instantiation ofWrapper<A>
, and so the list of candidates only contains the firsttest
, which is why this works. In the second case, the second overload is valid candidate sinceT
is explicitly provided, thus implying the instantiation ofWrapper<A>
which fails. – Inaptitude::type
member type? – HerthaWrapper<T>
types, it seemed like it worked like SFINAE sometimes but apparently it didn't. – Footcloth