使用 SFINAE 与 GCC 检测方法
Using SFINAE to detect method with GCC
我无法使用 SFINAE 概念来检测 class 是否具有某些编译器的特定模板方法,最相关的是 GCC。考虑下面的代码。它按预期与 MSVC 一起编译和工作;但是,当我用 GCC 编译相同的代码时,它会抱怨 std::vector<double>
没有序列化方法。当然,该方法不存在这一事实是真实的,但我预计这会导致替换失败,并且编译器会确定其他不太专业的回退是最合适的。我是否遗漏了有关 SFINAE 的信息,或者这是 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;
}
如果其他人遇到类似的 error/misunderstanding,我的错误(由 n.'pronouns' m. 恰当地指出)是在 has_serialize::test
中使用了错误的类型。我可以(天真地)推断出 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());
// ...
};
模板 C
没有出现在 has_serialize::test
中,因此 GCC 不提供替换失败的机会。因此,GCC 会尝试计算 std::declval<T>().serialize(...)
并在 T = std::vector<double>
时明显抛出错误。如果此行中 T
被替换为 C
,则编译器将其识别为替换失败并确定签名不合适。
此外,正如 Maxim Egorushkin 指出的那样,T::serialize
有可能 return 一个重载 operator,
的 user-defined 类型。为了改善(虽然不太可能)潜在的错误,代码应该是:
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());
// ...
};
我无法使用 SFINAE 概念来检测 class 是否具有某些编译器的特定模板方法,最相关的是 GCC。考虑下面的代码。它按预期与 MSVC 一起编译和工作;但是,当我用 GCC 编译相同的代码时,它会抱怨 std::vector<double>
没有序列化方法。当然,该方法不存在这一事实是真实的,但我预计这会导致替换失败,并且编译器会确定其他不太专业的回退是最合适的。我是否遗漏了有关 SFINAE 的信息,或者这是 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;
}
如果其他人遇到类似的 error/misunderstanding,我的错误(由 n.'pronouns' m. 恰当地指出)是在 has_serialize::test
中使用了错误的类型。我可以(天真地)推断出 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());
// ...
};
模板 C
没有出现在 has_serialize::test
中,因此 GCC 不提供替换失败的机会。因此,GCC 会尝试计算 std::declval<T>().serialize(...)
并在 T = std::vector<double>
时明显抛出错误。如果此行中 T
被替换为 C
,则编译器将其识别为替换失败并确定签名不合适。
此外,正如 Maxim Egorushkin 指出的那样,T::serialize
有可能 return 一个重载 operator,
的 user-defined 类型。为了改善(虽然不太可能)潜在的错误,代码应该是:
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());
// ...
};