用于确定类型是否具有方法的 SFINAE
SFINAE to determine if a type has a method
template <typename T>
struct has_xxx {
private:
using true_type = char;
using false_type = long;
template <typename C>
static true_type has_xxx_impl(decltype(&C::xxx)); // comment 1
template <typename C>
static false_type has_xxx_impl(...); // comment 2
public:
enum { value = sizeof(has_xxx_impl<T>(0)) == sizeof(true_type) }; // comment3
};
struct Foo { int xxx() {return 0;}; };
struct Foo2 {};
int main() {
static_assert(has_xxx<Foo>::value, "");
}
这是一个用于检测结构是否具有特定方法的结构。我对代码有一些疑问。
- 在评论 1 中,这个"&C"是什么意思,为什么我不能只写"C::xxx"
- 在注释 2 中,参数"..."是什么意思是,它是一个参数包,还是代表任何类型的参数
- 在评论 3 中,has_xxx_impl(0) 如何工作?T 被 Foo 替换,那么参数 0 怎么样?为什么选择第一个函数?
在评论 1 中,这个"&C"是什么意思,为什么我不能只写"C::xxx"
&
实际上指的是&C::xxx
的xxx
部分。也就是说,获取C
内成员xxx
的地址(希望这是一个函数,而不是静态成员---稍后会详细介绍)
在注释 2 中,参数"..."是什么意思是,它是一个参数包,还是代表任何类型的参数
...
或省略号运算符是一个接受任何内容的可变参数。这是一种非常 C 风格的做事方式,通常不是很安全。你今天最能看到的地方是吞咽例外:
try{
ThrowableFunctionCall();
} catch(...) // swallow any exceptions
{}
(注意:这不是参数包,也不是折叠表达式的一部分。请参阅 §5.2.2/6)
在评论 3 中,has_xxx_impl(0) 如何工作?T 被 Foo 替换,那么参数 0 怎么样?为什么选择第一个函数?
sizeof(has_xxx_impl<T>(0)) == sizeof(true_type)
是一种使用重载分辨率将值设置为 true_type
或 false_type
的聪明方法。请允许我解释一下:
乍一看调用has_xxx_impl<T>
会让人觉得
template <typename C>
static true_type has_xxx_impl(decltype(&C::xxx)); // comment 1
template <typename C>
static false_type has_xxx_impl(...); // comment 2
是可行的候选者,因为它们具有相同的名称,并且可能都可以使用类型 T
进行实例化。然而
decltype(&C::xxx)
正在尝试创建指向成员类型的指针(例如 int(C::*)()
成员函数指针),它是在类型推断的上下文中这样做的。如果语句格式不正确,则 SFINAE 生效并使其成为一个糟糕的候选者,只剩
template <typename C>
static false_type has_xxx_impl(...); // comment 2
由于...
匹配任何内容,因此该值设置为 false_type
或 long
.
如果语句格式正确,则可以将0
正确分配给函数指针类型(它不情愿地转换为具有null
值的指针类型)。这种转换优于匹配...
(自己尝试!),因此该值采用true_type
这是一个char
。
最后
value = sizeof(has_xxx_impl<T>(0)) == sizeof(true_type)
我们可以说是比较char
的大小和long
的大小(没有xxx
的情况)或 long
到 long
大小的大小(具有xxx
大小写)。
请注意,所有这些在 C++11/14 中都是完全不必要的,如果 xxx
是成员变量而不是函数,则确实会失败:
struct Foo3{static const int xxx;};
melak47的评论虽然更简单,但也有类似的缺点。
更好的解决方案是使用 is_member_function_pointer
类型特征:
template<typename...> // parameter pack here
using void_t = void;
template<typename T, typename = void>
struct has_xxx : std::false_type {};
template<typename T>
struct has_xxx<T, void_t<decltype(&T::xxx)>> :
std::is_member_function_pointer<decltype(&T::xxx)>{};
现场演示
以下解释是不完美的,没有引用标准。我试图尽可能容易理解地解释它。
has_xxx_impl(decltype(&C::xxx))
接受由decltype(&C::xxx)
产生的类型 &C::xxx
是指向成员的指针,decltype(&C::xxx)
是其类型。
另一个has_xxx_impl(...)
接受"任何东西"
因此,当你有has_xxx_impl<T>
编译器必须选择,上面之一。它更喜欢更好的匹配。因此,当类型包含xxx
时,第一个版本比...
版本匹配得更好。 如果它不包含这样的成员,则无法选择该函数,并且必须使用另一个(...
)
感谢SFINAE
- 替换失败(尝试传递不存在的成员类型时)不会产生错误(选择其他重载)
请注意,这两个函数返回不同的类型(true_type
和false_type
被定义为具有不同的大小),因此通过sizeof(has_xxx_impl<T>(0)) == sizeof(true_type)
您可以判断选择了哪一个。此比较的结果设置为可从外部访问的value
。
- 如何使用 SFINAE 在方法调用中有条件地定义变量?
- 有没有一种方法可以使用SFINAE来检测一个类型是否实现了给定的抽象基类
- 我的类中有方法的指针数组,但我不能调用我的方法.代码如下
- 如何处理一个子类有方法,而另一个没有方法的子类?
- 有方法的类,我不明白类的外观
- 是否有方法为模板参数指定所需的定义
- 使用sfinae来检测可变模板的基类是否有特定的方法
- 是否有方法将相对库路径添加到可执行文件以避免设置LD_library_path
- 是否有方法将所有赋值运算符(+=、*=等)转发为隐式使用重写的直接赋值运算符(=)
- 是否有一种方法可以使用SFINAE来确定对模板化函数的调用是否会由于所提供的类型而失败
- 是否有方法使用vector的内容作为键和自定义值来初始化unordered_map ?
- 在调用main函数之前,是否有方法解析命令行选项?
- 在c++中是否有方法对成员变量(类)进行后期初始化?
- 是否有方法为任何指针类型定义转换操作符
- 是否有方法为窗口窗体中的特定按钮挂钩鼠标事件
- 是否有方法检测内联函数ODR违规
- 在Windows Vista+上不注册proppage.dll,是否有方法访问远程过滤器图?
- 模板:只执行类中有方法的方法
- 是否有方法使Visual Studio对include区分大小写
- 是否有方法在调用函数时防止隐式的static_cast ?