PYA
PYA

Reputation: 8636

C++ constructor SFINAE

#include <iostream>

using namespace std;

template <typename T>
class test {
public:
    T value;

    template <typename... Args, typename = decltype(T())>
    test(Args... args): value(args...)
    {
       cout <<"ctor running\n";
    }

    template <typename... Args>
    test(Args...) : value(1)
    {
       cout <<"ctor unspec  running\n";
    }
};


class t
{
public:
    t() = delete;
    explicit t(int) {}
};


int main()
{
    test<t> h;
}

I am trying to call the second constructor for the object created (h). I do not know why I get this error:

prog.cc: In function 'int main()':
prog.cc:45:13: error: call of overloaded 'test()' is ambiguous
     test<t> h;
             ^
prog.cc:25:5: note: candidate: 'test<T>::test(Args ...) [with Args = {}; T = t]'
     test(Args... args)
     ^~~~
prog.cc:19:5: note: candidate: 'test<T>::test(Args ...) [with Args = {}; <template-parameter-2-2> = t; T = t]'
     test(Args... args): value(args...)
     ^~~~

I tried to make the entire class t private but that did not fix it either. I want the second constructor to run i.e. print `

"ctor unspec running"

What am I missing here? The first constructor call should be SFINAed away since typename = decltype(T()) wont work as t can not be default constructed but instead I get an ambiguous call error.

Upvotes: 4

Views: 918

Answers (1)

Guillaume Racicot
Guillaume Racicot

Reputation: 41780

SFINAE only happen with immediate context. Since T is a template argument of the class and not the template argument of the function, it is not immediate context. That means it becomes an "hard" error. It's an hard error because no matter what argument you send to the template argument of the constuctor, it will always be an error.

A solution would be to add a template argument equal to T, and use it to make SFINAE:

template <typename... Args, typename U = T, typename = decltype(U{})>
test(Args... args): value(args...)
{
   cout <<"ctor running\n";
}

Since U is immediate context, SFINAE is applied here.

With SFINAE, there is no ordering done. Every matching function is "equal", which means that if there is multiple matching function, there is no "better" one because it is constrained. So it would be a good idea to constrain the other one with a contrary constraint:

template <typename... Args, typename U = T,
    std::enable_if_t<!std::is_default_constructible<U>::value>* = nullptr>
test(Args...) : value(1)
{
   cout <<"ctor unspec  running\n";
}

Live example

Upvotes: 5

Related Questions