msvc visual c++从静态成员函数中不正确地形成绑定成员函数表达式

Posted

技术标签:

【中文标题】msvc visual c++从静态成员函数中不正确地形成绑定成员函数表达式【英文标题】:msvc visual c++ incorrect formation of bound member function expression from static member function 【发布时间】:2016-12-31 19:02:52 【问题描述】:

考虑以下代码:

#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 编译器都可以。

解决方法很简单:使用上面所有应该等价的任何其他表达式。

但是,错误消息的措辞令人不安,人们不得不怀疑,对于任何其他替代方案,绑定的成员函数表达式是否不正确。

对这个问题有什么见解吗?

【问题讨论】:

【参考方案1】:

对于语言律师来说,这是标准中一个不错的黑暗角落。

[expr.ref] ¶4.3

如果 E2 是(可能重载的)成员函数,函数重载决议用于确定 E1.E2 是指静态成员函数还是非静态成员函数

如果引用静态成员函数,并且E2的类型是“参数类型列表返回T的函数”,那么E1.E2是左值;表达式指定静态成员函数。 E1.E2的类型与E2的类型相同,即“参数类型列表返回T的函数”。

否则,如果 E1.E2 引用一个非静态成员函数并且 E2 的类型是“参数类型列表的函数 cv ref-qualifieropt 返回 T",则 E1.E2 是纯右值。该表达式指定一个非静态成员函数。 表达式只能用作成员函数调用的左侧操作数。 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 的指针,无论是否使用 &amp; 地址操作符。 MSVC 的不一致之处在于它在没有&amp; 的情况下成功,但没有它。但是由于上述原因,很难说哪个是一致的。 GCC 在所有情况下都非常乐意让成员访问表达式指定一个未解决的重载,然后由于初始化的目标类型([over.over] ¶1)而解决该重载。

SFINAE 参与进来会更有趣:

[temp.over] ¶1

...如果对于给定的函数模板,参数推导失败或合成的函数模板特化格式不正确,则不会将此类函数添加到该模板的候选函数集中...

因此,为了确定成员函数的“静态性”,一些编译器可能会从候选函数集中排除 SFINAE 失败以进行重载解析。但同样,这里很难说什么是一致的。这可以解释为什么 Clang 接受原始问题中的示例,而不是我的 bar 示例。我感觉实施者只是在尽最大努力填补这里的空白。

【讨论】:

以上是关于msvc visual c++从静态成员函数中不正确地形成绑定成员函数表达式的主要内容,如果未能解决你的问题,请参考以下文章

visual C++中error C2352是啥意思

C++ 多个版本的默认特殊成员函数 -- MSVC 2015 中的错误

从 C (Visual Studio) 调用 C++ 类成员函数

静态代码分析器:非托管C ++ Visual Studio 2008

C++派生类是不是可以从基类继承静态数据成员和静态成员函数?

C++之静态