user1494080 user1494080 - 1 month ago 14
C++ Question

Using std::enable_if with anonymous type parameters

I try to use

std::enable_if
with an unused and unnamed type parameter, in order to not distort the
return
type. However, the following code does not compile.

#include <iostream>

template <typename T, typename = std::enable_if_t<!std::is_integral<T>::value>>
T foo() { std::cout << "non-integral" << std::endl; return T(); }

template <typename T, typename = std::enable_if_t<std::is_integral<T>::value>>
T foo() { std::cout << "integral" << std::endl; return T(); }

int main() {
foo<float>();
foo<int>();
}


The compiler says:

7:3: error: redefinition of 'template<class T, class> T foo()'
4:3: note: 'template<class T, class> T foo()' previously declared here
In function 'int main()':
11:12: error: no matching function for call to 'foo()'
11:12: note: candidate is:
4:3: note: template<class T, class> T foo()
4:3: note: template argument deduction/substitution failed:


What is the problem here? How do I have to change the code to get it compile? The text book "Discovering Modern C++" explicitly encourages the use of
std::enable_if
with anonymous type parameters.

EDIT: I know that it works if I put
std::enable_if
into the return type. However, my intention is to get some more details why it does not work if I use it with anonymous type parameters. As I said, my text book encourages the variant using anonymous type parameters, so I am wondering why my code does not compile.

Answer

However, my intention is to get some more details why it does not work if I use it with anonymous type parameters.

Default values do not participate in overload resolution, thus you are actually redefining the same function.

Let's simplify your example:

template<typename = int>
void f() {}

template<typename = void>
void f() {}

int main() {
    f<>();
}

The code above does not compile, for it couldn't know what version of f you want to invoke.

In your case, if I invoke foo as foo<void, void>, I've almost the same problem.
The compiler cannot guess what's my intention and the fact that the second parameter has a default value doesn't mean that you can't pass in a different type.

Because of that, the code is ill-formed and the compiler correctly gives you an error.


As a side note, you can still have it working without using the std::enable_if_t in the return type.
As an example:

#include <type_traits>
#include <iostream>

template <typename T, std::enable_if_t<!std::is_integral<T>::value>* = nullptr>
T foo() { std::cout << "non-integral" << std::endl; return T(); }

template <typename T, std::enable_if_t<std::is_integral<T>::value>* = nullptr>
T foo() { std::cout << "integral" << std::endl; return T(); }

int main() {
    foo<float>();
    foo<int>();
}