尝试使用 SFINAE 实施类型检查时出错

Error trying to implement type checking with SFINAE

我关注了 Jean Guegant 的 SFINAE 博客 post,在那里他使用 sizeof() 实现了类型特征,它检查是否有 serialize 中的函数 class。在其中他有这个 reallyHas 结构,它检查序列化是否只是 class 的成员或实际的成员函数,通过这样做

template<typename T>
struct has_serialize {
    typedef char yes[1];
    typedef char no[2];

    template<typename U, U u> struct reallyHas{}; 

    template <typename C>
    static yes& test(reallyHas<string (C::*)(), &C::serialize>*){}

    // for const method
    template<typename C>
    static yes& test(reallyHas<string (C::*)() const, &C::serialize>*){}

    template<typename> // variadic template
    static no& test(...){} // sink hole

    enum {
         value = (sizeof(test<T>(0)) == sizeof(yes))
    };
};

仅当测试函数具有

的签名时才能正常工作
template <typename C>
static yes& test(reallyHas<string (C::*)(), &C::serialize>*){}

但不是

template <typename C>
static yes& test(reallyHas<string (C::*)(), &C::serialize>){}

我不太明白参数中*的意义,最后我们只是检查是否发生了替换,所以它不应该在两种情况下都有效

简化,SFINAE 的原则是为一个符号(重载、模板特化等)提供一些候选。然后,对于给定的参数,编译器将一次尝试每个候选者,直到找到最佳匹配。

在这种情况下,test函数用于SFINAE。目标是以这样一种方式定义它,即对于具有 serialize 成员函数的类型,它将解析为 test 的函数 returns 一个 yes 类型,对于一个否则返回 no 类型的函数。

上面显示的示例通过使用将 0 转换为指针的技巧来实现此目的。这种转换是有可能按照语言规则发生的。

test<T>(0) 被调用时,编译器将首先查看第一个重载并通过替换 T 最终得到一个如下所示的函数:

static yes& test(reallyHas<string (T::*)(), &T::serialize>*){}

编译器现在将查看是否可以选择此重载。它将查看 0 (int) 是否可以转换为 reallyHas<string (T::*)(), &T::serialize>*。由于 int 可以转换为指针,因此仅当类型 reallyHas<string (T::*)(), &T::serialize>* 格式不正确时才会发生错误。如果 T 没有合适的成员函数 &T::serialize 可以转换为函数指针 string (T::*)(),就会出现这种情况。例如,对于 using T = int,它将失败,因为 int 甚至不是 class 类型。

如果替换失败,编译器会类推地尝试第二次重载,最后是第三次重载。由于第三个被声明为 static no& test(...){} 它可以接受任何参数。在这种情况下,它也可以声明为 static no& test(unsigned){} 因为 0 可以转换为 unsigned.

现在请注意如果将第一个重载更改为此会发生什么:

template <typename C>
static yes& test(reallyHas<string (C::*)(), &C::serialize>){}

现在替换失败总是会发生(即使 T 有一个成员 serialize 函数)因为 0 (int) 不能转换为 class 输入 reallyHas<T>.