Me myself and I
Me myself and I

Reputation: 4070

How do I do proper SFINAE on inheriting constructors?

I'm trying to do SFINAE on a constructor. I want to enable one overload for integers and one for everything else. I know I can just make a base(int) and base(T) constructor but I want to do it this way instead.

template <class T>
struct base
{
    template <class T1 = T>
    base(T1, typename std::enable_if<std::is_same<T1, int>::value>::type* = nullptr) {}

    template <class T1 = T>
    base(T1, typename std::enable_if<!std::is_same<T1, int>::value>::type* = nullptr) {}
};

Then I make a main Base class that inherits the constructors:

template <class T>
struct Base : base<T>
{
    using base<T>::base;
};

But when I instantiate Base with any T I get these errors:

source_file.cpp:21:15: error: call to deleted constructor of 'Base<int>'
    Base<int> v(4);
              ^ ~
source_file.cpp:16:25: note: deleted constructor was inherited here
    using base<T>::base;
                        ^
source_file.cpp:7:5: note: constructor cannot be inherited
    base(T1, typename std::enable_if<std::is_same<T1, int>::value>::type* = nullptr) {}
    ^

When I instantiate base directly it works without a problem. Why can't the constructor be inherited when I am doing SFINAE? Without the second constructor overload everything works fine.

Upvotes: 1

Views: 164

Answers (2)

Potatoswatter
Potatoswatter

Reputation: 137880

Unlike the parameter list of a constructor, the entire template parameter list is always inherited including default template arguments. Put the SFINAE there:

template <class T>
struct base
{
    template <class T1 = T,
      typename std::enable_if<std::is_same<T1, int>::value>::type* = nullptr>
    base(T1) {}

    template <class T1 = T,
      typename std::enable_if<!std::is_same<T1, int>::value>::type* = nullptr>
    base(T1) {}
};

This works in Clang and GCC, in -std=c++11 and -std=c++1z modes. (Only recent versions tested, 3.6 and 5.1, respectively.)

Upvotes: 0

Casey
Casey

Reputation: 42574

You could avoid this problem altogether in the example program by defining only the generic constructor in base and providing a specialization for base<int> (DEMO):

template <class T>
struct base
{
    base(T) { std::cout << "generic constructor\n"; }
};

template <>
base<int>::base(int) { std::cout << "int specialization\n"; }

or use tag dispatching instead of SFINAE (DEMO):

template <class T>
class base
{
    base(std::true_type, int) { std::cout << "int specialization\n"; }
    base(std::false_type, T) { std::cout << "generic constructor\n"; }

public:
    base(T t) : base(std::is_same<int, T>{}, std::move(t)) {}
};

Upvotes: 2

Related Questions