Reputation: 6999
I am trying to leave in struct A
one function foo
(prints 0) if its parameter have template method isA<void>
and another (prints 1) if haven't. This code (reduced to minimal example below) compiles (tried with gcc 6.1.0 and clang-3.9.0 with explicit --std=c++14
option) and runs.
But it prints 1, though, I am sure, that it shall print 0. I wonder where am I wrong, but real question is: how to make this work correct?
Please only C++14 solutions.
#include <type_traits>
#include <iostream>
#include <utility>
using std::enable_if;
using std::declval;
using std::true_type;
using std::false_type;
using std::cout;
template<int M>
struct ObjectX
{
template<typename C>
bool isA() { return false; }
};
struct XX : ObjectX<23456> {
int af;
};
template <typename ObjType> using has_dep = decltype(declval<ObjType>().template isA<void>());
template <typename, typename = void>
struct has_isa : public false_type {};
template <typename ObjType>
struct has_isa<ObjType, has_dep<ObjType> > : public true_type {};
template<typename ObjType>
struct A
{
template<typename T = void>
typename enable_if<has_isa<ObjType>::value, T>::type
foo() {
cout << "called foo #0" << "\n";
}
template<typename T = void>
typename enable_if<!has_isa<ObjType>::value, T>::type
foo() {
cout << "called foo #1" << "\n";
}
};
int
main()
{
A<XX> axx;
// XX().template isA<void>(); -- to check, that we can call it and it exists
axx.foo();
return 0;
}
Upvotes: 3
Views: 115
Reputation: 41780
Your sfinae fails because has_isa
chooses the wrong specialization.
The use of has_isa<T>
must be either the default implementation or the specialized version.
As you defined, you have a default argument to void:
// default argument ---------v
template <typename, typename = void>
struct has_isa : public false_type {};
Then in the expression has_isa<T>
, the second parameter must be void. It's roughly the same as writing has_isa<T, void>
.
The problem is this:
template <typename ObjType>
struct has_isa<ObjType, has_dep<ObjType>> : public true_type {};
// ^--- what's that type?
Even though template partial ordering would consider this "overload" more specialized, it won't be chosen. Look at the definition of has_dep
:
struct XX {
template<typename C> bool isA() { return false; }
};
template <typename ObjType>
using has_dep = decltype(declval<ObjType>().template isA<void>());
Hey, that type has_dep<T>
is the return type of t.isA<void>()
which is bool
!
So the specialized version look like this:
template <typename ObjType>
struct has_isa<ObjType, has_dep<ObjType>> : public true_type {};
// ^--- really, this is bool in our case
So in order for this to work, you must call has_isa<T, bool>
. As this is impractical, you should define your specialization as this:
template <typename ObjType>
struct has_isa<ObjType, void_t<has_dep<ObjType>>> : public true_type {};
Where void_t
is defined as so:
template<typename...>
using void_t = void; // beware for msvc
As such, has_isa<T>
will always consider the specialization, because we send void
as the second template parameter, and now our specialization always result with void
as second parameter.
Also, as stated by Barry, your function is not correctly formed as sfinae only appears in immediate context. You should write it like this:
template<typename T = ObjType>
typename enable_if<has_isa<T>::value, void>::type
foo() { // ^--- sfinae happens with T
cout << "called foo #0" << "\n";
}
If you don't wish to expose the template parameter, simply make the function private:
template<typename ObjType>
struct A {
public:
void foo() {
foo_impl();
}
private:
template<typename T = ObjType>
typename enable_if<has_isa<T>::value, void>::type
foo_impl() {
cout << "called foo #0" << "\n";
}
template<typename T = ObjType>
typename enable_if<!has_isa<T>::value, void>::type
foo_impl() {
cout << "called foo #1" << "\n";
}
};
Upvotes: 2
Reputation: 303087
There are two problems in this program.
First, has_dep<XX>
is bool
. When we try has_dep<XX>
, adding the default template arguments means this is really has_dep<XX, void>
. But the specialization is has_dep<XX, bool>
- which doesn't match what we're actually looking up. bool
does not match void
. That's why has_dep<XX>
is false_type
. The solution to this is std::void_t
, and I'd suggest reading through that Q/A to get an idea for why it works. In your specialization, you need to use void_t<has_dep<ObjType>>
instead.
Second, this is not right:
template<typename T = void>
typename enable_if<has_isa<ObjType>::value, T>::type
SFINAE only happens in the immediate context of substitution, and class template parameters are not in the immediate context of function template substitution. The right pattern here is:
template <typename T = ObjType> // default to class template parameter
enable_if_t<has_isa<T>> // use the function template parameter to SFINAE
foo() { ... }
Make those two fixes, and the program works as intended.
Upvotes: 4
Reputation: 217283
Your problem is that you specialize the wrong class:
You should force has_dep
to return void
.
template <typename ObjType> using has_dep = decltype(static_cast<void>(declval<ObjType>().template isA<void>()));
So here
template <typename ObjType>
struct has_isa<ObjType, has_dep<ObjType> > : public true_type {};
// It is really <bjType, void> you specialize.
Upvotes: 1