I have read the following threads:
no type named ‘type’ in ‘struct std::enable_if<false, void>
Selecting a member function using different enable_if conditions
"What happened to my SFINAE" redux: conditional template class members?
However, I seem to be unable to make this fairly simple SFINAE problem work on either gcc and msvc:
#include <type_traits>
#include <iostream>
template<typename A, typename B>
class Test {
public:
template<typename X=A, typename = typename std::enable_if<std::is_same<X, void>::value, void>::type >
void foo() {
std::cout << "A";
}
template<typename X=A, typename = typename std::enable_if<!std::is_same<X, void>::value, void>::type >
void foo() {
std::cout << "B";
}
};
int main(int argc, char **argv) {
Test<int, float> t;
t.foo();
return 0;
}
Actual result:
A = void: Full error:
main.cpp:15:8: error: 'template<class A, class B> template<class X, class> void Test<A, B>::foo()' cannot be overloaded with 'template<class A, class B> template<class X, class> void Test<A, B>::foo()'
15 | void foo() {
| ^~~
main.cpp:10:8: note: previous declaration 'template<class A, class B> template<class X, class> void Test<A, B>::foo()'
10 | void foo() {
| ^~~
A = int: Full error:
main.cpp:15:8: error: 'template<class A, class B> template<class X, class> void Test<A, B>::foo()' cannot be overloaded with 'template<class A, class B> template<class X, class> void Test<A, B>::foo()'
15 | void foo() {
| ^~~
main.cpp:10:8: note: previous declaration 'template<class A, class B> template<class X, class> void Test<A, B>::foo()'
10 | void foo() {
| ^~~
main.cpp: In function 'int main(int, char**)':
main.cpp:26:9: error: no matching function for call to 'Test<int, float>::foo()'
26 | t.foo();
| ^
main.cpp:10:8: note: candidate: 'template<class X, class> void Test<A, B>::foo() [with X = X; <template-parameter-2-2> = <template-parameter-1-2>; A = int; B = float]'
10 | void foo() {
| ^~~
main.cpp:10:8: note: template argument deduction/substitution failed:
main.cpp:9:26: error: no type named 'type' in 'struct std::enable_if<false, void>'
9 | template<typename X=A, typename = typename std::enable_if<std::is_same<X, void>::value, void>::type >
| ^~~~~~~~
Expected result
A = void: Outputs "A"
A = int: Outputs "B"
What I want is to implement a different (additional) member function based on a template parameter.
However, it seems like I cannot make the enable_if dependent on the class template types, but I am not sure why. According to the linked threads, the code above appears correct.
Could you please explain why this is not working?
Live Link
The notes from cppreference show similar and explains why it does not work:
A common mistake is to declare two function templates that differ only in their default template arguments. This does not work because the declarations are treated as redeclarations of the same function template (default template arguments are not accounted for in function template equivalence).
/* WRONG */
struct T {
enum { int_t,float_t } m_type;
template <typename Integer,
typename = std::enable_if_t<std::is_integral<Integer>::value>
>
T(Integer) : m_type(int_t) {}
template <typename Floating,
typename = std::enable_if_t<std::is_floating_point<Floating>::value>
>
T(Floating) : m_type(float_t) {} // error: treated as redefinition
};
/* RIGHT */
struct T {
enum { int_t,float_t } m_type;
template <typename Integer,
std::enable_if_t<std::is_integral<Integer>::value, int> = 0
>
T(Integer) : m_type(int_t) {}
template <typename Floating,
std::enable_if_t<std::is_floating_point<Floating>::value, int> = 0
>
T(Floating) : m_type(float_t) {} // OK
};
Applying the same fix to your code, makes it output the desired B :
#include <type_traits>
#include <iostream>
template<typename A, typename B>
class Test {
public:
template<typename X = A,std::enable_if_t<std::is_same<X, void>::value, int> = 0>
void foo() {
std::cout << "A";
}
template<typename X=A,std::enable_if_t<!std::is_same<X, void>::value, int> = 0>
void foo() {
std::cout << "B";
}
};
In your code the two function templates differed only in their default arguments. After the fix the second parameter is either int = 0 or a substition failure.
Here's a C++17 version:
template<typename X=A>
std::enable_if_t<std::is_same_v<X, void>> foo() {
std::cout << "A";
}
template<typename X=A>
std::enable_if_t<!std::is_same_v<X, void>> foo() {
std::cout << "B";
}
(The default type for enable_if is void which is used as the type for the function)
You could also make it with constexpr if:
void foo() {
if constexpr (std::is_same_v<A, void>) {
std::cout << "A";
} else {
std::cout << "B";
}
}
C++11:
template<typename X=A>
typename std::enable_if<std::is_same<X, void>::value>::type foo() {
std::cout << "A";
}
template<typename X=A>
typename std::enable_if<!std::is_same<X, void>::value>::type foo() {
std::cout << "B";
}
Related
The below code compiles on clang (I tried 7 and 8), but not gcc (7, 8, or 9), all using --std=c++17. With gcc I get an ambiguous template instantiation error, as you can see here: https://godbolt.org/z/69Bomq.
I can fix it for gcc by uncommenting #define FIX_GCC. If I change first_type<C> and first_type<const C> to C and const C in the specializations of traits<Map<...>>, it also compiles, but in my real code if have some enable_if_t<...> to narrow down the specialization there.
Is gcc wrong here? Or am I doing something wrong that clang happens to accept?
Update: Removing one layer of templates, i.e. Map, in the specialization also makes GCC happy here even with the template alias first_type, see https://godbolt.org/z/90kGEP.
<source>: In function 'int main()':
<source>:36:38: error: ambiguous template instantiation for 'struct traits<Map<const Foo> >'
36 | std::cout << traits<Map<const Foo>>::N << std::endl;
| ^~
<source>:24:8: note: candidates are: 'template<class C> struct traits<Map<first_type<C> > > [with C = const Foo]'
24 | struct traits<Map<first_type<C>>> {
| ^~~~~~~~~~~~~~~~~~~~~~~~~~
<source>:30:8: note: 'template<class C> struct traits<Map<first_type<const C> > > [with C = Foo]'
30 | struct traits<Map<first_type<const C>>> {
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
<source>:36:40: error: incomplete type 'traits<Map<const Foo> >' used in nested name specifier
36 | std::cout << traits<Map<const Foo>>::N << std::endl;
| ^
#include <iostream>
#include <type_traits>
template <class T, class...>
using first_type = T;
class Foo {};
template <class C>
class Map {};
template <class C>
struct traits {};
//#define FIX_GCC
#ifdef FIX_GCC
template <typename C>
struct traits<Map<first_type<C, std::enable_if_t<!std::is_const_v<C>>>>> {
static constexpr int N = 2;
};
#else
template <typename C>
struct traits<Map<first_type<C>>> {
static constexpr int N = 2;
};
#endif
template <typename C>
struct traits<Map<first_type<const C>>> {
static constexpr int N = 3;
};
int main() {
std::cout << traits<Map<Foo>>::N << std::endl;
std::cout << traits<Map<const Foo>>::N << std::endl;
return 0;
}
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 have a code like this
#include <iostream>
struct X {
};
template <typename T>
struct A {
static int a();
};
template <>
int A<int>::a() {
return 42;
}
template <>
int A<X>::a() {
return 0;
}
int main() {
std::cout << A<int>().a() << std::endl;
std::cout << A<X>().a() << std::endl;
return 0;
}
Now I want to return 42 for all types that are arithmetic i.e such that std::is_arithmetic<T>::type is std::true_type
I've tried
template <typename T, typename U = std::true_type>
struct A {
static int a();
};
template <typename T>
int A<T, typename std::is_arithmetic<T>::type>::a() {
return 42;
}
But I got following error:
a.cpp:12:51: error: invalid use of incomplete type ‘struct A<T, typename std::is_arithmetic<_Tp>::type>’
int A<T, typename std::is_arithmetic<T>::type>::a() {
^
a.cpp:7:8: error: declaration of ‘struct A<T, typename std::is_arithmetic<_Tp>::type>’
struct A {
^
and also tried
template <typename T>
struct A {
static int a();
};
template <typename T, typename E = typename std::enable_if<std::is_arithmetic<T>::value>::type>
int A<T>::a() {
return 42;
}
Error:
a.cpp:12:13: error: default argument for template parameter for class enclosing ‘static int A<T>::a()’
int A<T>::a() {
^
a.cpp:12:13: error: got 2 template parameters for ‘static int A<T>::a()’
a.cpp:12:13: error: but 1 required
What is correct way to achieve this? Does it exist at all?
I know I can do that, specializing all struct at once, but I don't want this because there are actually several functions more, that should be common
No, that's not going to work. Instead, try defining a separate type that will contain either 42 or 0, based on is_arithmetic. You can use boost::mpl::if_ for the purpose:
template< typename T > struct Res
{
typedef typename if_<
std::is_arithmetic<T>
, static_value<42>
, static_value<0>
>::type value;
};
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{});
}