3

Problem

Suppose we have a (fictional) class template C<T> with a conditionally explicit default constructor. The default constructor should be explicit if and only if std::is_same_v<T, int>.

A search on "[c++] conditionally explicit" returns this result: Constructor conditionally marked explicit.

A failed solution

The accepted answer gives an example:

struct S {
  template <typename T,
             typename std::enable_if<std::is_integral<T>::value, bool>::type = false >
  S(T) {}

  template <typename T,
            typename std::enable_if<!std::is_integral<T>::value, bool>::type = false>
  explicit S(T) {}
};

Modifying the example slightly gives this implementation which uses the familiar approach of std::enable_if:

template <class T>
class C {
public:
  template <std::enable_if_t<std::is_same_v<T, int>, int> = 0>
  C() {}

  template <std::enable_if_t<!std::is_same_v<T, int>, int> = 0>
  explicit C() {}
};

Unfortunately, this does not even compile: demo

prog.cc: In instantiation of 'class C<int>':
prog.cc:15:10:   required from here
prog.cc:10:12: error: no type named 'type' in 'struct std::enable_if<false, int>'
   10 |   explicit C() {}
      |            ^
prog.cc: In instantiation of 'class C<double>':
prog.cc:18:13:   required from here
prog.cc:7:3: error: no type named 'type' in 'struct std::enable_if<false, int>'
    7 |   C() {}
      |   ^

The problem seems to be caused by the omission of a template parameter of the constructor, disabling SFINAE.

Question

  1. Why does this not compile?
  2. What is a possible implementation?

I would like to avoid specializing the class if possible.

L. F.
  • 19,445
  • 8
  • 48
  • 82

1 Answers1

4
  1. What is a possible implementation?

Have you tried with

template <class T>
class C {
public: //  VVVVVVVVVVVVVV .................................V  U here, not T
  template <typename U = T, std::enable_if_t<std::is_same_v<U, int>, int> = 0>
  C() {}

  template <typename U = T, std::enable_if_t<!std::is_same_v<U, int>, int> = 0>
  explicit C() {}
};

?

  1. Why does this not compile?

The problem is that SFINAE, over class methods, works with the template parameters of the methods itself.

That is in the original working code:

  template <typename T,
             typename std::enable_if<std::is_integral<T>::value, bool>::type = false >
  S(T) {}

where T is a template parameter specific of the constructor (deduced from the single argument).

On the contrary, in your failing code,

template <std::enable_if_t<std::is_same_v<T, int>, int> = 0>
C() {}

the constructors are evaluating the template parameter of the class (T), not of the methods.

With the trick typename U = T, you transform T, the template parameter of the class, in U, a template parameter of the methods (constructors in your case, but works also with other methods) so std::enable_if_t, with a test depending from U, is able to enable/disable the constructors.

max66
  • 65,235
  • 10
  • 71
  • 111
  • Thank you for your detailed explanation! I have to admit that C++ template meta-programming is quite counter-intuitive at times ... – L. F. Apr 28 '19 at 11:30
  • @L.F. - And this is nothing: if you try to enable/disable both constructors with `typename = std::enable_if_t<!std::is_same_v` instead of `std::enable_if_t<!std::is_same_v, int> = 0`, you should get an error because the constructors collide. – max66 Apr 28 '19 at 11:33
  • Yeah, exactly. I guess that’s more common so I happened to know it. – L. F. Apr 28 '19 at 11:34