尝试使用 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>
.
我关注了 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>
.