指向成员的指针的 C++ 类内初始化使 MSVC 失败(但 GCC/Clang 工作)

C++ in-class initialization of pointer-to-member makes MSVC to fail (but GCC/Clang work)

提问人:i.stav 提问时间:7/23/2019 最后编辑:i.stav 更新时间:7/23/2019 访问量:365

问:

下面是一个非常简单的 C++ 代码:

#include <iostream>

struct A
{
    int a;
    constexpr static int A::* p = &A::a;
    virtual void f() {}
};
int main()
{
    A x; x.a = 0;
    x.*(A::p) = 1234;
    std::cout << x.a;
}

更令人震惊的是,这段代码显示了GCC,Clang和MSVC之间的不同结果。

我尝试了 4 个编译器

  1. GCC:编译良好,打印.1234

  2. Clang :编译良好,打印 .1234

  3. MSVC(联机):编译失败。

  4. 使用 Visual Studio 2019 的 MSVC(本地):编译良好,打印 .(有趣的是,如果我删除它,它就会打印出来。0f()1234

我不确定使用自己的成员对指向成员的指针进行类内初始化是否合法,但我相信这段代码应该打印.1234

Compiler ExplorerRextester 上试用。

我不知道谁是对的。(至少 4.似乎是一个错误)

编辑 - 我发现 3 的差异。和 4.来自编译器选项。但是 和 no- 的结果仍然与 gcc 和 clang 不同。/permissive/permissive/permissive

C 工作室 可视化 C++

评论

4赞 Some programmer dude 7/23/2019
我更感兴趣的是你为什么会有这样的东西?你试图用这种方式解决的真正问题是什么?
1赞 i.stav 7/23/2019
@Someprogrammerdude,我将原始代码提炼得非常简单,但足以重现错误。(这是在 stackoverflow 上发布问题的正确方式)这个小的虚拟代码是没有意义的。
0赞 Some programmer dude 7/23/2019
我想你误会我了。问题本身很好,例子也很好。我只是想知道你最初试图解决什么问题,是什么让你想出这样的解决方案?或者这只是单纯的好奇心(这和其他任何事情一样好)?
1赞 i.stav 7/23/2019
@Someprogrammerdude 它非常复杂(并且与此代码非常不同),所以我只想说在模板元编程中打包成员。(您可以将指针到成员扩展到元组中的指针到成员)
0赞 sklott 7/23/2019
GCC 4.8.4 编译失败,出现分段错误:)

答:

0赞 Windsting 7/23/2019 #1

更新

这是一个略微修改的代码,它似乎在 Visual Studio 2019 中按预期工作:

struct A
{
    int a;
    constexpr static int A::* p() { return &A::a; }
    virtual void f() {}
};

using namespace std;
int main()
{
    A x;
    void* px = &x;
    x.a = 0;
    x.*(A::p()) = 1234;
    cout << x.a << endl;

    getchar();
}

更新结束


刚刚在 Visual Studio 2019 上尝试过,我将代码修改为:

#include <iostream>

struct A
{
    int a;
    constexpr static int A::* p = &A::a;
    virtual void f() {}
};

struct B
{
    int a;
    constexpr static int B::* p = &B::a;
};

using namespace std;

void printBytes(void* p, size_t length = 16) {
    uint8_t* pu = (uint8_t*)p;
    cout << "  ";
    for (size_t i = 0; i < length; ++i) {
        auto val = *(pu + i);
        printf("%02x ", val);
    }
    cout << endl;
}

int main()
{
    A x;
    void* px = &x;
    cout << "\nfor A:\n";
    printBytes(px);
    x.a = 0;
    printBytes(px);
    x.*(A::p) = 0x1234;
    printBytes(px);
    cout << x.a << endl;

    B y;
    void* py = &y;
    cout << "\nfor B:\n";
    printBytes(py);
    y.a = 0;
    printBytes(py);
    y.*(B::p) = 0x1234;
    printBytes(py);
    cout << y.a << endl;

    getchar();
}

输出为:


for A:
  fc 9c 2c 01 cc cc cc cc cc cc cc cc 0f 40 04 09
  fc 9c 2c 01 00 00 00 00 cc cc cc cc 0f 40 04 09
  34 12 00 00 00 00 00 00 cc cc cc cc 0f 40 04 09
0

for B:
  cc cc cc cc cc cc cc cc cc cc cc cc 60 fd 3d 01
  00 00 00 00 cc cc cc cc cc cc cc cc 60 fd 3d 01
  34 12 00 00 cc cc cc cc cc cc cc cc 60 fd 3d 01
4660

B 案例按预期工作,但对于 A 案例,上面的输出表明以下行:

x.*(A::p) = 0x1234;

写入到“指向虚拟功能表的指针”所在的位置。0x1234

因此,我认为 Visual Studio 2019 的编译器在具有虚拟成员函数的 struct(class and also )上的表达式(获取指向成员变量的指针)时犯了一个错误。&A::a

评论

0赞 i.stav 7/23/2019
井。。。那么这不是一个严重的错误..?
0赞 Windsting 7/23/2019
我不知道它有多严重,但我认为可以通过绕道来解决:改成constexpr static int A::* p = &A::a;constexpr static int A::* PA() { return &A::a; }