是使用引入一个新的成员函数还是只是一个别名?

Jos*_*eph 5 c++ gcc language-lawyer c++17 visual-studio-2017

下面的最小示例在 MSVC 17 上编译得很好,但在 GCC 8.2 上会产生编译错误。哪个编译器是对的?这段代码在 C++17 中是否正确?

#include <iostream>

class A
{
public:

    A() = default;

protected:

    void foo(int x)
    { std::cout << x << std::endl; }
};

class B : private A
{
    using Method_t = void (B::*)(int);
    using A::foo;

    template <Method_t M>
    void baz()
    { (this->*M)(42); }

public:

    B() = default;

    void bar()
    { baz<&B::foo>(); }
};

int main()
{
    B().bar();
}
Run Code Online (Sandbox Code Playgroud)

GCC 错误是:

mwe.cpp:29:20: error: could not convert template argument '&A::foo' from 'void (A::*)(int)' to 'void (B::*)(int)'
Run Code Online (Sandbox Code Playgroud)

Ast*_*ngs 4

这很有趣。

\n

根据当前规则*,其目的似乎是foo保留基地的成员,而不是引入实际的成员B

\n

尽管事实上重载解析现在可以在中找到该成员B

\n
\n

[namespace.udecl/15][注意:为了在重载解析期间形成一组候选者,由using 声明引入派生类的函数将被视为派生类的成员 ([class.member.lookup] )。特别是,隐式对象参数被视为对派生类而不是基类的引用 ([over.match.funcs])。这对函数的类型没有影响,并且在所有其他方面该函数仍然是基类的成员。 \xe2\x80\x94 尾注]

\n
\n

尽管事实上,在代码中,B::bar可以引用该成员(即不必拼写为A::bar):

\n
\n

[expr.prim.id.qual/2]表示类的嵌套名称说明符,可以选择后跟关键字template([temp.names]),然后后跟该类 ([class.mem]) 或其基类之一的成员的名称,是一个合格的 id;[class.qual] 描述出现在qualified-ids中的类成员的名称查找。结果就是会员。结果的类型是成员的类型。 [..]

\n
\n

但成员的实际类型是void (A::*)(int)

\n

没有规则允许转换为void (B::*)(int),即使是专门针对以这种方式引入的成员的规则(显然这种转换通常是无效的)。

\n

因此,我认为 Visual Studio 是错误的。

\n

* 为了方便起见,我引用了当前的草案,但没有理由相信该规则最近发生了变化;GCC 和 Clang 都拒绝所有 C++11、C++14 和 C++17 中的代码。

\n
\n

顺便说一句,这实际上也不能使用最新版本的 Visual Studio 进行编译

\n
<source>(29): error C2672: \'B::baz\': no matching overloaded function found\n<source>(29): error C2893: Failed to specialize function template \'void B::baz(void)\'\n<source>(21): note: see declaration of \'B::baz\'\n<source>(29): note: With the following template arguments:\n<source>(29): note: \'M=void A::foo(int)\'\n
Run Code Online (Sandbox Code Playgroud)\n

所以,也许他们已经修复了自您的版本以来的错误。VS 中的兼容模式也可能是罪魁祸首。

\n