【问题标题】:msvc visual c++ incorrect formation of bound member function expression from static member functionmsvc visual c++从静态成员函数中不正确地形成绑定成员函数表达式
【发布时间】: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 编译器都可以。

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

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

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

【问题讨论】:

    标签: c++ visual-c++ visual-studio-2015 compiler-bug


    【解决方案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 示例。我感觉实施者只是在尽最大努力填补这里的空白。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-03-02
      • 1970-01-01
      相关资源
      最近更新 更多