Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SFINAE works with deduction but fails with substitution

Consider the following MCVE

struct A {};

template<class T>
void test(T, T) {
}

template<class T>
class Wrapper {
    using type = typename T::type;
};

template<class T>
void test(Wrapper<T>, Wrapper<T>) {
}

int main() {
    A a, b;
    test(a, b);     // works
    test<A>(a, b);  // doesn't work
    return 0;
}

Here test(a, b); works and test<A>(a, b); fails with:

<source>:11:30: error: no type named 'type' in 'A'
    using type = typename T::type;
                 ~~~~~~~~~~~~^~~~
<source>:23:13: note: in instantiation of template class 'Wrap<A>' requested here
    test<A>(a, b);  // doesn't work
            ^
<source>:23:5: note: while substituting deduced template arguments into function template 'test' [with T = A]
    test<A>(a, b);  // doesn't work

LIVE DEMO

Question: Why is that? Shouldn't SFINAE work during substitution? Yet here it seems to work during deduction only.

like image 269
rustyx Avatar asked Apr 30 '19 12:04

rustyx


2 Answers

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:

  1. 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.

  2. 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.

like image 199
L. F. Avatar answered Oct 19 '22 07:10

L. F.


I'm not a language lawyer but I don't think that defining a using type = typename T::type; inside a class is, itself, usable as SFINAE to enable/disable a function receiving an object of that class.

If you want a solution, you can apply SFINAE to the Wrapper version as follows

template<class T>
auto test(Wrapper<T>, Wrapper<T>)
   -> decltype( T::type, void() )
 { }

This way, this test() function is enabled only for T types with a type type defined inside it.

In your version, is enabled for every T type but gives error when T is incompatible with Wrapper.

-- EDIT --

The OP precises and asks

My Wrapper has many more dependencies on T, it would be impractical to duplicate them all in a SFINAE expression. Isn't there a way to check if Wrapper itself can be instantiated?

As suggested by Holt, you can create a custom type traits to see if a type is a Wrapper<something> type; by example

template <typename>
struct is_wrapper : public std::false_type
 { };

template <typename T>
struct is_wrapper<Wrapper<T>> : public std::true_type
 { using type = T; };

Then you can modify the Wrapper version to receive a U type and check if U is a Wrapper<something> type

template <typename U>
std::enable_if_t<is_wrapper<U>{}> test (U, U)
 { using T = typename is_wrapper<U>::type; }

Observe that you can recover the original T type (if you need it) using the type definition inside the is_wrapper struct.

If you need a non-Wrapper version of test(), with this solution you have to explicity disable it when T is a Wrapper<something> type to avoid collision

template <typename T>
std::enable_if_t<!is_wrapper<T>{}> test(T, T)
 { }
like image 4
max66 Avatar answered Oct 19 '22 07:10

max66