2

I don't understand why the first (good) version of the code compiles, but the second doesn't

I have read this, this , this, and of course this but i still do not understand why for one version it compiles, while for the other it doesn't. If somebody could please explain it (like for total dummies), I would really appreciate it.

GOOD version

template <typename As, typename std::enable_if<
std::is_arithmetic<As>::value, As>::type* = nullptr   > 
As getStringAs(const std::string& arg_name)
{
    std::istringstream istr(arg_name);
    As val;
    istr >> val;
    if (istr.fail())
        throw std::invalid_argument(arg_name);
    return val;
}

BAD version

template <typename As, typename std::enable_if_t<
std::is_arithmetic<As>::value, As> = 0   > 
As getStringAs(const std::string& arg_name)
{
    std::istringstream istr(arg_name);
    As val;
    istr >> val;
    if (istr.fail())
        throw std::invalid_argument(arg_name);
    return val;
}

Intended Usage:

int main()
{
   return getStringAs<float>("2.f");
}

Thank you very much!

eucristian
  • 391
  • 3
  • 17
  • `std::enable_if_t` is already `typename std::enable_if::type`. – Jarod42 Apr 25 '19 at 22:43
  • 5
    Because you cannot have a non-type template parameter of floating point type. – David G Apr 25 '19 at 22:44
  • @0x499602D2 thank you very much for your answer. Now i know what to google:) – eucristian Apr 25 '19 at 23:03
  • 1
    @0x499602D2 Considered making it an answer? There are two answers as of right now and neither of them actually answer the question. One is plain wrong and one just explains how enable_if works but not why this doesn't compile. – Creris Apr 25 '19 at 23:50

3 Answers3

2

std::enable_if_t<std::is_arithmetic<As>::value, As> substitutes to As assuming the condition is true. The reason the error is emitted is because you cannot have a non-type template parameter of floating point type. In this case you don't appear to be using the template parameter for any reason except SFINAE so you can replace the second As with int and it should compile.

std::enable_if_t<std::is_arithmetic<As>::value, int> = 0
David G
  • 94,763
  • 41
  • 167
  • 253
0

std::enable_if is a type, and I can declare variables with it:

std::enable_if<true, int> myVar;

You could also write:

std::enable_if<true, int> myVar2{};
std::enable_if<true, int> myVar3 = {};

It doesn't have a constructor that takes an integer, so this fails to compile:

//Error - no way to convert 0 to std::enable_if<true, int>
std::enable_if<true, int> myVar = 0; 

// Error - no way to convert nullptr to std::enable_if<true, int>
std::enable_if<true, int> myVar = nullptr; 

In the same way, typename std::enable_if<true, int>::type* is a pointer (specifically a int*). It can be assigned 0, and it can also be assigned to nullptr:

// This works, because you can assign 0 to a pointer
typename std::enable_if<true, int>::type* myPtr = 0; 
// This works, because you can assign nullptr to a pointer
typename std::enable_if<true, int>::type* myPtr = nullptr; 

How enable_if works. enable_if is built on a hack where, under certain circumstances, a compiler will just ignore an instance of a templated function if it fails to compile. (NB: if the declaration compiles, but the body doesn't, the compiler can't ignore that).

Let's say we have two versions of a function, and you want to switch between them based on some condition:

// This version gets called if T::value is true, because it'll fail to compile otherwise
template<class T, typename std::enable_if<T::value>::type* = nullptr>
void foo(){
    std::cout << "T::value is true\n";
}

// This version gets called if T::value is false, because it'll fail to compile otherwise
template<class T, typename std::enable_if<not T::value>::type* = nullptr>
void foo(){
    std::cout << "T::value is false\n"; 
}

If you have two classes, both with a constexpr value member, it'll call the correct version of the function:

class A{
    public:
    constexpr static bool value = true;
};
class B {
    public:
    constexpr static bool value = false;
};
int main() {
    foo<A>(); // Prints T::value is true
    foo<B>(); // Prints T::value is false
}
Alecto Irene Perez
  • 10,321
  • 23
  • 46
-1

You forgot an asterisk, and have an unneeded typename:

template <typename As, /*typename*/ std::enable_if_t<
std::is_arithmetic<As>::value, As>* = 0   > 
                         // here  ^
yuri kilochek
  • 12,709
  • 2
  • 32
  • 59