请考虑以下代码:
#include <type_traits>
struct A {
template<typename T, std::enable_if_t<sizeof(T)<=sizeof(int), int> = 0>
static void fun() {}
template<typename T, std::enable_if_t<(sizeof(T)>sizeof(int)), int> = 0>
static void fun() {}
void test() {
using fun_t = void(*)();
fun_t ff[] = {
fun<int>,
&fun<int>,
A::fun<int>,
&A::fun<int>,
this->fun<int>,
&this->fun<int>, //error C2276: '&': illegal operation on bound member function expression
};
}
};
msvc 2015update3和2017rc生成错误C2276,这没有任何意义。 gcc,clang和intel编译器都没问题。
解决方法很简单:使用上面的任何其他表达式都应该是等价的。
但是,错误消息的措辞是令人不安的,并且不得不怀疑,如果为任何其他替代方案错误地形成了绑定成员函数表达式。
对此事有何见解?
答案 0 :(得分:1)
对于语言律师来说,这是一个很好的黑暗角落。
[expr.ref]¶4.3
如果E2是(可能是重载的)成员函数,函数重载决策用于确定E1.E2是指静态成员函数还是非静态成员函数。
如果它引用静态成员函数并且E2的类型是“参数类型列表返回T的函数”,则E1.E2是左值;表达式指定静态成员函数。 E1.E2的类型与E2的类型相同,即“参数类型列表返回T的函数”。
否则,如果E1.E2引用非静态成员函数,并且E2的类型是“参数类型列表 cv ref-qualifier opt <的函数/ em>返回T“,然后E1.E2是prvalue。表达式指定非静态成员函数。 表达式只能用作成员函数调用的左侧操作数。 E1.E2的类型是“参数类型列表cv返回T的函数”。
不言而喻,只有指向 static 成员函数的指针才能由成员访问表达式(即点或箭头运算符)形成。但需要注意的是,重载决策决定了成员函数的“静态”。所以问题是,如何在没有参数列表的情况下执行重载决策?编译器的解释似乎有所不同。
有些编译器可能会认识到,因为表达式用于形成指向函数的指针,函数必须是静态成员函数,否则程序生成错误,因此排除非静态成员函数来自候选函数集。
其他编译器可能会认识到因为所有重载都是静态的,所以重载解析不可能选择非静态成员函数,因此完全跳过该步骤。
然而,其他编译器可能完全放弃,面对在没有任何参数列表的情况下应用重载决策的指令。
这是一个相关的例子:
struct A {
static void foo(int) {}
static void bar(int) {}
static void bar(double) {}
};
void test() {
A a;
void(*f)(int) = a.foo; // gcc:OK, msvc:OK, clang:OK
void(*g)(int) = a.bar; // gcc:OK, msvc:OK, clang:ERROR
void(*h)(int) = &a.bar; // gcc:OK, msvc:ERROR, clang:ERROR
}
这里Clang无法使用成员访问表达式形成指向int
bar
重载的指针,无论是否有&
address-of运算符。 MSVC不一致,因为它在没有&
但没有bar
的情况下成功。但由于上述原因,很难说哪个符合要求。在所有情况下,GCC都非常高兴让成员访问表达式指定一个未解析的重载,然后由于初始化的目标类型([over.over]¶1)而解决该重载。
当SFINAE参与时,它会变得更有趣:
[temp.over]¶1
...如果对于给定的函数模板,参数推导失败或者合成的函数模板特化将是格式错误的,则不会将该函数添加到该模板的候选函数集中...
因此,为了确定成员函数的“静态性”,一些编译器可以从用于重载解析的候选函数集中排除SFINAE失败。但同样,很难说这里符合什么。这可以解释为什么Clang接受原始问题中的示例,而不是我的SELECT to_char((col1 - col0), 'HH24 hrs MI "minutes" SS "seconds"') FROM T1;
示例。我觉得实施者正在尽力填补这里的空白。