I just asked this question: std::numeric_limits as a Condition
I understand the usage where std::enable_if will define the return type of a method conditionally causing the method to fail to compile.
template<typename T>
typename std::enable_if<std::numeric_limits<T>::is_integer, void>::type foo(const T &bar) { isInt(bar); }
What I don't understand is the second argument and the seemingly meaningless assignment to std::enable_if when it's declared as part of the template statement, as in Rapptz answer.
template<typename T, typename std::enable_if<std::is_integral<T>::value, int>::type = 0>
void foo(const T& bar) { isInt(); }
As is mentioned in comment by 40two, understanding of Substitution Failure Is Not An Error is a prerequisite for understanding std::enable_if.
std::enable_if is a specialized template defined as:
template<bool Cond, class T = void> struct enable_if {};
template<class T> struct enable_if<true, T> { typedef T type; };
The key here is in the fact that typedef T type is only defined when bool Cond is true.
Now armed with that understanding of std::enable_if it's clear that void foo(const T &bar) { isInt(bar); } is defined by:
template<typename T>
typename std::enable_if<std::numeric_limits<T>::is_integer, void>::type foo(const T &bar) { isInt(bar); }
As mentioned in firda's answer, the = 0 is a defaulting of the second template parameter. The reason for the defaulting in template<typename T, typename std::enable_if<std::is_integral<T>::value, int>::type = 0> is so that both options can be called with foo< int >( 1 );. If the std::enable_if template parameter was not defaulted, calling foo would require two template parameters, not just the int.
General note, this answer is made clearer by explicitly typing out typename std::enable_if<std::numeric_limits<T>::is_integer, void>::type but void is the default second parameter to std::enable_if, and if you have c++14 enable_if_t is a defined type and should be used. So the return type should condense to: std::enable_if_t<std::numeric_limits<T>::is_integer>
A special note for users of visual-studio prior to visual-studio-2013: Default template parameters aren't supported, so you'll only be able to use the enable_if on the function return: std::numeric_limits as a Condition
template<typename T, std::enable_if<std::is_integral<T>::value, int>::type = 0>
void foo(const T& bar) { isInt(); }
this fails to compile if T is not integral (because enable_if<...>::type won't be defined). It is protection of the function foo.The assignment = 0 is there for default template parameter to hide it.
Another possibility: (yes the typename is missing in original question)
#include <type_traits>
template<typename T, typename std::enable_if<std::is_integral<T>::value, int>::type = 0>
void foo(const T& bar) {}
template<typename T>
typename std::enable_if<std::is_integral<T>::value>::type
bar(const T& foo) {}
int main() {
foo(1); bar(1);
foo("bad"); bar("bad");
}
error: no matching function for call to ‘foo(const char [4])’
foo("bad"); bar("bad");
^
note: candidate is:
note: template::value, int>::type > void foo(const T&)
void foo(const T& bar) {}
^
note: template argument deduction/substitution failed:
error: no type named ‘type’ in ‘struct std::enable_if’
template::value, int>::type = 0>
^
note: invalid template non-type parameter
error: no matching function for call to ‘bar(const char [4])’
foo("bad"); bar("bad");
^
Related
I'm practicing SFINAE and would like to implement a type trait in order to check if a given class T contains a method print(). I have the following two variants:
// (1)
template <typename T, typename = int>
struct has_print_method : std::false_type {};
template <typename T>
struct has_print_method<T, decltype(&T::print, 0)> : std::true_type {};
template <typename T>
const bool has_print_method_v = has_print_method<T>::value;
// (2)
template <typename T>
struct has_print_method{
template <typename U, typename = void> struct helper : std::false_type{};
template <typename U> struct helper<U, decltype(&U::print)> : std::true_type{};
static const bool value = helper<T, void (T::*)() const>::value;
};
template <typename T>
const bool has_print_method_v = has_print_method<T>::value;
(1) only checks for the existence of a member method print() and ignores the member method's signature. Whereas (2) checks the method's signature, i.e. it requires a member method void print() const.
I test both variants via:
#include <iostream>
#include <type_traits>
// Simple Class A
struct A{
int a;
public:
void print() const{}
};
// (1) or (2) here
template<typename T, std::enable_if_t<has_print_method_v<T>, bool> = true>
void print(T t) {
t.print();
}
void print(double x){
std::cout << x << '\n';
}
int main() {
A a;
print(a);
print(1.0); // (*)
return 0;
}
Using the type trait (1) and compiling with clang 12.0 and std=c++17 flag works as expected. However, using (2) instead, I obtain
<source>:28:50: error: member pointer refers into non-class type 'double'
static const bool value = helper<T, void (T::*)() const>::value;
^
<source>:32:33: note: in instantiation of template class 'has_print_method<double>' requested here
const bool has_print_method_v = has_print_method<T>::value;
^
<source>:34:39: note: in instantiation of variable template specialization 'has_print_method_v<double>' requested here
template<typename T, std::enable_if_t<has_print_method_v<T>, bool> = true>
^
<source>:35:6: note: while substituting prior template arguments into non-type template parameter [with T = double]
void print(T t) {
^~~~~~~~~~~~
<source>:47:5: note: while substituting deduced template arguments into function template 'print' [with T = double, $1 = (no value)]
print(1.0);
^
1 error generated.
What am I missing here? You can find the example here on godbolt. Edit: Um, I have just noticed that both versions compile without an error with gcc11.1. Strange.
First up, judging by your error messages and my own tests, I think you mean that type trait (1) works and (2) doesn't. On that basis, here is a version of trait (1) that tests for a matching function signature:
template <typename T, typename = int>
struct has_print_method : std::false_type {};
template <typename T>
struct has_print_method<T, decltype(&T::print, 0)> : std::is_same <decltype(&T::print), void (T::*)() const> {};
template <typename T>
const bool has_print_method_v = has_print_method<T>::value;
Live demo
I already used the SFINAE idiom quite a few times and I got used to put my std::enable_if<> in template parameters rather than in return types. However, I came across some trivial case where it didn't work, and I'm not sure why. First of all, here is my main:
int main()
{
foo(5);
foo(3.4);
}
Here is an implementation of foo that triggers the error:
template<typename T,
typename = typename std::enable_if<std::is_integral<T>::value>::type>
auto foo(T)
-> void
{
std::cout << "I'm an integer!\n";
}
template<typename T,
typename = typename std::enable_if<std::is_floating_point<T>::value>::type>
auto foo(T)
-> void
{
std::cout << "I'm a floating point number!\n";
}
And here is a supposedly equivalent piece of code that works fine:
template<typename T>
auto foo(T)
-> typename std::enable_if<std::is_integral<T>::value>::type
{
std::cout << "I'm an integrer!\n";
}
template<typename T>
auto foo(T)
-> typename std::enable_if<std::is_floating_point<T>::value>::type
{
std::cout << "I'm a floating point number!\n";
}
My question is: why does the first implementation of foo triggers that error while the second one does not trigger it?
main.cpp:14:6: error: redefinition of 'template<class T, class> void foo(T)'
auto foo(T)
^
main.cpp:6:6: note: 'template<class T, class> void foo(T)' previously declared here
auto foo(T)
^
main.cpp: In function 'int main()':
main.cpp:23:12: error: no matching function for call to 'foo(double)'
foo(3.4);
^
main.cpp:6:6: note: candidate: template<class T, class> void foo(T)
auto foo(T)
^
main.cpp:6:6: note: template argument deduction/substitution failed:
main.cpp:5:10: error: no type named 'type' in 'struct std::enable_if<false, void>'
typename = typename std::enable_if<std::is_integral<T>::value>::type>
^
EDIT :
Working code and faulty code.
You should take a look at 14.5.6.1 Function template overloading (C++11 standard) where function templates equivalency is defined. In short, default template arguments are not considered, so in the 1st case you have the same function template defined twice. In the 2nd case you have expression referring template parameters used in the return type (again see 14.5.6.1/4). Since this expression is part of signature you get two different function template declarations and thus SFINAE get a chance to work.
Values in the templates work:
template<typename T,
typename std::enable_if<std::is_integral<T>::value, int>::type = 0>
auto foo(T)
-> void
{
std::cout << "I'm an integer!\n";
}
template<typename T,
typename std::enable_if<std::is_floating_point<T>::value, int>::type = 0>
auto foo(T)
-> void
{
std::cout << "I'm a floating point number!\n";
}
The = ... of the template just gives a default parameter. This is not part of the actual signature which looks like
template<typename T, typename>
auto foo(T a);
for both functions.
Depending on your needs, the most generic solution for this problem is using tag dispatching.
struct integral_tag { typedef integral_tag category; };
struct floating_tag { typedef floating_tag category; };
template <typename T> struct foo_tag
: std::conditional<std::is_integral<T>::value, integral_tag,
typename std::conditional<std::is_floating_point<T>::value, floating_tag,
std::false_type>::type>::type {};
template<typename T>
T foo_impl(T a, integral_tag) { return a; }
template<typename T>
T foo_impl(T a, floating_tag) { return a; }
template <typename T>
T foo(T a)
{
static_assert(!std::is_base_of<std::false_type, foo_tag<T> >::value,
"T must be either floating point or integral");
return foo_impl(a, typename foo_tag<T>::category{});
}
struct bigint {};
template<> struct foo_tag<bigint> : integral_tag {};
int main()
{
//foo("x"); // produces a nice error message
foo(1);
foo(1.5);
foo(bigint{});
}
I already used the SFINAE idiom quite a few times and I got used to put my std::enable_if<> in template parameters rather than in return types. However, I came across some trivial case where it didn't work, and I'm not sure why. First of all, here is my main:
int main()
{
foo(5);
foo(3.4);
}
Here is an implementation of foo that triggers the error:
template<typename T,
typename = typename std::enable_if<std::is_integral<T>::value>::type>
auto foo(T)
-> void
{
std::cout << "I'm an integer!\n";
}
template<typename T,
typename = typename std::enable_if<std::is_floating_point<T>::value>::type>
auto foo(T)
-> void
{
std::cout << "I'm a floating point number!\n";
}
And here is a supposedly equivalent piece of code that works fine:
template<typename T>
auto foo(T)
-> typename std::enable_if<std::is_integral<T>::value>::type
{
std::cout << "I'm an integrer!\n";
}
template<typename T>
auto foo(T)
-> typename std::enable_if<std::is_floating_point<T>::value>::type
{
std::cout << "I'm a floating point number!\n";
}
My question is: why does the first implementation of foo triggers that error while the second one does not trigger it?
main.cpp:14:6: error: redefinition of 'template<class T, class> void foo(T)'
auto foo(T)
^
main.cpp:6:6: note: 'template<class T, class> void foo(T)' previously declared here
auto foo(T)
^
main.cpp: In function 'int main()':
main.cpp:23:12: error: no matching function for call to 'foo(double)'
foo(3.4);
^
main.cpp:6:6: note: candidate: template<class T, class> void foo(T)
auto foo(T)
^
main.cpp:6:6: note: template argument deduction/substitution failed:
main.cpp:5:10: error: no type named 'type' in 'struct std::enable_if<false, void>'
typename = typename std::enable_if<std::is_integral<T>::value>::type>
^
EDIT :
Working code and faulty code.
You should take a look at 14.5.6.1 Function template overloading (C++11 standard) where function templates equivalency is defined. In short, default template arguments are not considered, so in the 1st case you have the same function template defined twice. In the 2nd case you have expression referring template parameters used in the return type (again see 14.5.6.1/4). Since this expression is part of signature you get two different function template declarations and thus SFINAE get a chance to work.
Values in the templates work:
template<typename T,
typename std::enable_if<std::is_integral<T>::value, int>::type = 0>
auto foo(T)
-> void
{
std::cout << "I'm an integer!\n";
}
template<typename T,
typename std::enable_if<std::is_floating_point<T>::value, int>::type = 0>
auto foo(T)
-> void
{
std::cout << "I'm a floating point number!\n";
}
The = ... of the template just gives a default parameter. This is not part of the actual signature which looks like
template<typename T, typename>
auto foo(T a);
for both functions.
Depending on your needs, the most generic solution for this problem is using tag dispatching.
struct integral_tag { typedef integral_tag category; };
struct floating_tag { typedef floating_tag category; };
template <typename T> struct foo_tag
: std::conditional<std::is_integral<T>::value, integral_tag,
typename std::conditional<std::is_floating_point<T>::value, floating_tag,
std::false_type>::type>::type {};
template<typename T>
T foo_impl(T a, integral_tag) { return a; }
template<typename T>
T foo_impl(T a, floating_tag) { return a; }
template <typename T>
T foo(T a)
{
static_assert(!std::is_base_of<std::false_type, foo_tag<T> >::value,
"T must be either floating point or integral");
return foo_impl(a, typename foo_tag<T>::category{});
}
struct bigint {};
template<> struct foo_tag<bigint> : integral_tag {};
int main()
{
//foo("x"); // produces a nice error message
foo(1);
foo(1.5);
foo(bigint{});
}
I tried to use std::enable_if on a function parameter to trigger SFINAE. Compilation fails with this error:
type_nonsense.cpp:20:5: error: no matching function for call to 'c'
c(SOME::VALUE);
^
type_nonsense.cpp:13:6: note: candidate template ignored: couldn't infer
template argument 'T'
void c(typename std::enable_if<std::is_enum<T>::value, T>::type t) {}
^
1 error generated.
Moving the std::enable_if to either the return type or to a dummy template parameter works fine. Why?
#include <type_traits>
// Works
template <typename T, typename dummy = typename std::enable_if<std::is_enum<T>::value, T>::type>
void a(T t) {}
// Works
template <typename T>
typename std::enable_if<std::is_enum<T>::value, void>::type b(T t) {}
// Fails to compile
template <typename T>
void c(typename std::enable_if<std::is_enum<T>::value, T>::type t) {}
enum class SOME { VALUE };
int main() {
a(SOME::VALUE);
b(SOME::VALUE);
c(SOME::VALUE);
}
A dependent type in a nested name specifier is a non-deduced context for template argument deduction, and as such cannot be used to determine the type of T. Placing the std::enable_if in either the return type or as a default template parameter works because the type of T isn't being deduced in those contexts.
If you need to place it as a parameter, you can do so like this:
template <typename T>
void c(T t, typename std::enable_if<std::is_enum<T>::value>::type* = nullptr) {}
This works because T is being deduced by the first argument, not the second.
For the one which fails to compile, T is not deductible.
I already used the SFINAE idiom quite a few times and I got used to put my std::enable_if<> in template parameters rather than in return types. However, I came across some trivial case where it didn't work, and I'm not sure why. First of all, here is my main:
int main()
{
foo(5);
foo(3.4);
}
Here is an implementation of foo that triggers the error:
template<typename T,
typename = typename std::enable_if<std::is_integral<T>::value>::type>
auto foo(T)
-> void
{
std::cout << "I'm an integer!\n";
}
template<typename T,
typename = typename std::enable_if<std::is_floating_point<T>::value>::type>
auto foo(T)
-> void
{
std::cout << "I'm a floating point number!\n";
}
And here is a supposedly equivalent piece of code that works fine:
template<typename T>
auto foo(T)
-> typename std::enable_if<std::is_integral<T>::value>::type
{
std::cout << "I'm an integrer!\n";
}
template<typename T>
auto foo(T)
-> typename std::enable_if<std::is_floating_point<T>::value>::type
{
std::cout << "I'm a floating point number!\n";
}
My question is: why does the first implementation of foo triggers that error while the second one does not trigger it?
main.cpp:14:6: error: redefinition of 'template<class T, class> void foo(T)'
auto foo(T)
^
main.cpp:6:6: note: 'template<class T, class> void foo(T)' previously declared here
auto foo(T)
^
main.cpp: In function 'int main()':
main.cpp:23:12: error: no matching function for call to 'foo(double)'
foo(3.4);
^
main.cpp:6:6: note: candidate: template<class T, class> void foo(T)
auto foo(T)
^
main.cpp:6:6: note: template argument deduction/substitution failed:
main.cpp:5:10: error: no type named 'type' in 'struct std::enable_if<false, void>'
typename = typename std::enable_if<std::is_integral<T>::value>::type>
^
EDIT :
Working code and faulty code.
You should take a look at 14.5.6.1 Function template overloading (C++11 standard) where function templates equivalency is defined. In short, default template arguments are not considered, so in the 1st case you have the same function template defined twice. In the 2nd case you have expression referring template parameters used in the return type (again see 14.5.6.1/4). Since this expression is part of signature you get two different function template declarations and thus SFINAE get a chance to work.
Values in the templates work:
template<typename T,
typename std::enable_if<std::is_integral<T>::value, int>::type = 0>
auto foo(T)
-> void
{
std::cout << "I'm an integer!\n";
}
template<typename T,
typename std::enable_if<std::is_floating_point<T>::value, int>::type = 0>
auto foo(T)
-> void
{
std::cout << "I'm a floating point number!\n";
}
The = ... of the template just gives a default parameter. This is not part of the actual signature which looks like
template<typename T, typename>
auto foo(T a);
for both functions.
Depending on your needs, the most generic solution for this problem is using tag dispatching.
struct integral_tag { typedef integral_tag category; };
struct floating_tag { typedef floating_tag category; };
template <typename T> struct foo_tag
: std::conditional<std::is_integral<T>::value, integral_tag,
typename std::conditional<std::is_floating_point<T>::value, floating_tag,
std::false_type>::type>::type {};
template<typename T>
T foo_impl(T a, integral_tag) { return a; }
template<typename T>
T foo_impl(T a, floating_tag) { return a; }
template <typename T>
T foo(T a)
{
static_assert(!std::is_base_of<std::false_type, foo_tag<T> >::value,
"T must be either floating point or integral");
return foo_impl(a, typename foo_tag<T>::category{});
}
struct bigint {};
template<> struct foo_tag<bigint> : integral_tag {};
int main()
{
//foo("x"); // produces a nice error message
foo(1);
foo(1.5);
foo(bigint{});
}