5
#include <type_traits>

template<bool b>
struct S
{
    template<typename = std::enable_if_t<b>>
        S() {}
    template<typename = std::enable_if_t<!b>>
        S(int) {}
};

S<true> s{}; // error in clang/gcc, OK in VC2017
S<false> s{0}; // error in clang/gcc, OK in VC2017

In both cases clang/gcc try to instantiate the ctor that should actually be discarded due to SFINAE. The error message is:

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

clang/gcc's instantiation of the other ctor is incorrect since it should not be on the list of possible overloads, right?

But before I file a bug I would like to read what others think. Maybe I don't get it right...

melpomene
  • 84,125
  • 8
  • 85
  • 148
x y
  • 557
  • 3
  • 10

2 Answers2

11

This is a bug in MSVC; clang and gcc are right.

The problem is that SFINAE happens during overload resolution only, not before. What I mean is, if the function is ill-formed even before you call it, it's an error.

When you use S<true> for example, the whole class is instantiated. It will look a bit like this:

struct S_true
{
    template<typename = void>
    S() {}

    template<typename = /*fail*/>
    S(int) {}
};

As you can see, the second constructor is completely ill-formed, it's not a valid definition, because there is no type type found (because of std::enable_if). So SFINAE cannot even kick in, the class definition is ill-formed and diagnosed.

You need to make the template parameter b part of the template parameter list of both constructors (have a look at @bolov's answer).

Rakete1111
  • 47,013
  • 16
  • 123
  • 162
  • 1
    if you want you can incorporate my answer into yours (no attribution needed) and I will delete mine. – bolov Oct 06 '17 at 11:48
  • Thanks to all of you for these immediate answers! Not even I could believe that clang/gcc are doing it wrong and VC is doing it right. Seems I need to report this bug to Microsoft instead. – x y Oct 06 '17 at 12:30
  • @bolov Nah, I didn't know of that technique :) Thanks – Rakete1111 Oct 06 '17 at 14:16
  • @bolov I'm curious: according to C++17 17.7.8.1 (latest draft) an implementation does not need to report an error if a template specialization is invalid but not instantiated ("ill-formed, no diagnostics required"). For S the member function template specialization for S(int) is invalid but not instantiated because S() is called. And for S the invalid S() is not instantiated because S(int) is called. So is it really a bug in VC to not report an error? I'm skeptical. What do you think? – x y Oct 09 '17 at 12:03
  • 1
    @xy I don't know, didn't look into it, but my feeling is that all the ctors need to be instantiated in order to resolve the overload resolution. If such is the case then it should issue a warning and it is a VS bug for not doing so. I could be wrong about all ctors needing to be instantiated, in which case VS would be conforming to not issue an error. – bolov Oct 09 '17 at 12:11
  • @xy bolov is right, function templates are instantiated during overload resolution. [over.match.funcs/p7](http://eel.is/c++draft/over.match.funcs#7) – Rakete1111 Oct 09 '17 at 16:06
6

@Rakete1111 is 100% right.

You need to make the template parameter bool b part of the template parameter list of both constructors.

Here is how to do this (it's a pretty standard technique):

template<bool b>
struct S
{
    template<bool bb = b, typename = std::enable_if_t<bb>>
        S() {}
    template<bool bb = b, typename = std::enable_if_t<!bb>>
        S(int) {}
};
bolov
  • 72,283
  • 15
  • 145
  • 224