-1

I have two constructors that I would like to choose between based on a template parameter yes

template <bool yes>
class Base {
public:
  template<typename std::enable_if< yes, int>::type = 0>
  Base() { /* yes */ }

  template<typename std::enable_if<!yes, int>::type = 0>
  Base() { /* no */ }
};

I'm baffled why this produces the compiler error,

failed requirement '!true'; 'enable_if' cannot be used to disable this declaration

on Base<true> and

no type named 'type' in 'std::__1::enable_if<false, int>'; 'enable_if' cannot be used to disable this declaration

on Base<false>. None of the other variants that I can find including this and this and this work either. How can I select which constructor to use based on yes?

Jon Deaton
  • 3,943
  • 6
  • 28
  • 41
  • Wouldn't it be simpler to write `if constexpr (yes) { /* ... */ } else { /* ... */ }` in a single, non-templated constructor? It would amount to the same thing. Since you're having compiler troubles, what version of C++ are you targeting? You'll probably need C++11, and for `if constexpr` you'll need C++17 – alter_igel May 04 '19 at 00:45
  • I would but its the initializer list that actually differs between these constructors. I'm currently targeting C++14, but would be fine with switching to C++17 if there was a working solution that I could get for it. – Jon Deaton May 04 '19 at 00:46

1 Answers1

1

There are several issues here. The first thing is that the syntax for default template template paramers is wrong, it should be:

template <bool yes>
class Base {
public:
  template<typename T=std::enable_if< yes, int>::type>
  Base() { /* yes */ }

  template<typename T=std::enable_if<!yes, int>::type>
  Base() { /* no */ }
};

But that's not going to work either, because default parameter values are not a part of the template's signature, so, roughly speaking, this is equivalent to:

  template<typename T>
  Base() { /* yes */ }

  template<typename T>
  Base() { /* no */ }

That's how the both constructors' signatures look to the compiler. Both are templates with a single parameter, so for the purposes of overload resolutions both constructors have identical signatures, and that's not going to work any better than declaring two "Base(int foo)" constructors. You'll get the same error if you declare:

Base(int foo=0)

and

Base(int foo=1)

constructors. Two constructors, both have the same signature. Default values are not a part of a signature.

There are several traditional hacks to work around this. A common design pattern in the C++ library itself is to declare some helper empty classes and use them as additional parameters to disambiguate different methods for the purposes of overload resolution. For example, using std::in_place_t to select a particular overloaded constructor of std::optional, or std::in_place_type_t, for the equivalent functionality of std::variant's constructor.

In your case here, we can make use of the placeholder parameter completely automatic, in conjunction with delegated constructors:

#include <iostream>

struct bool_true {};
struct bool_false {};

template<bool value> class bool_value;

template<>
struct bool_value<true> {

    typedef bool_true type;
};

template<>
struct bool_value<false> {

    typedef bool_false type;
};

template<bool v>
using bool_value_t=typename bool_value<v>::type;


template <bool yes>
class Base {
public:

    Base() : Base{ bool_value_t<yes>{} } {}

    Base(const bool_true &)
    {
        std::cout << "Yes" << std::endl;
    }

    Base(const bool_false &)
    {
        std::cout << "No" << std::endl;
    }
};

int main()
{
    Base<true> t;
    Base<false> f;
    return 0;
}
Sam Varshavchik
  • 114,536
  • 5
  • 94
  • 148