Reputation: 71
I am having trouble using the SFINAE concept to detect if a class has a particular template method with some compilers, most pertinently GCC. Consider the code below. It compiles and works as expected with MSVC; however, when I compile the same code with GCC, it complains that std::vector<double>
does not have a serialize method. Of course, the fact that the method does not exist is true, but I expected this to result in a substitution failure and the compiler determining that the other less specialized fallback is most suitable. Am I missing something about SFINAE or is this a bug with GCC?
#include <iostream>
#include <utility>
#include <vector>
class SerializerBase
{
public:
SerializerBase() {}
};
template<typename T>
struct has_serialize
{
template<typename C>
static constexpr auto test(...) -> std::false_type;
/// Note: this is where the compiler complains about the .serialize method
/// during substitution.
template<typename C>
static constexpr auto test(int)
-> decltype(std::declval<T>().serialize(std::declval<SerializerBase&>()), std::true_type());
using result_type = decltype(test<T>(0));
static const bool value = result_type::value;
};
class Serializer : public SerializerBase
{
public:
Serializer() {}
template<typename T,
typename std::enable_if<!has_serialize<T>::value, T>::type* = nullptr>
void operator()(const T& v)
{
std::cout << "fallback called" << std::endl;
}
template<typename T,
typename std::enable_if<has_serialize<T>::value, T>::type* = nullptr>
void operator()(const T& v)
{
v.serialize(*this);
}
};
struct Foo
{
template<typename SerializerType>
void serialize(SerializerType& s) const
{
std::cout << "serialize called" << std::endl;
}
};
int main()
{
Serializer s;
std::vector<double> v;
Foo f;
s(v);
s(f);
return 0;
}
Upvotes: 3
Views: 101
Reputation: 71
In the event someone else encounters a similar error/misunderstanding, my error (aptly pointed out by n. 'pronouns' m.) was using the wrong type in the has_serialize::test
. From what I can infer (in my naivety) is that for
template<typename T>
struct has_serialize
{
// ...
template<typename C>
static constexpr auto test(int)
-> decltype(std::declval<T>().serialize(std::declval<SerializerBase&>()), std::true_type());
// ...
};
the template C
does not appear in has_serialize::test
, and as a result GCC does not provide an opportunity for substitution failure. Consequently, GCC tries to evaluate std::declval<T>().serialize(...)
and obviously throws an error when T = std::vector<double>
. If T
is replaced with C
in this line, then the compiler recognizes it as a substitution failure and determines the signature to be unsuitable.
Furthermore, as Maxim Egorushkin pointed out, there is the possibility that T::serialize
might return a user-defined type that overloads operator,
. To ameliorate (albeit very unlikely) potential error, the code should be:
template<typename T>
struct has_serialize
{
// ...
template<typename C>
static constexpr auto test(int)
-> decltype(static_cast<void>(std::declval<C>().serialize(std::declval<SerializerBase&>())), std::true_type());
// ...
};
Upvotes: 4