使用 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());
    // ...
};