Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Shall using introduce a new member function or just an alias?

Tags:

The following minimal example compiles fine with MSVC 17 but produces a compilation error on GCC 8.2. Which compiler is right? Is this code correct in 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();
}

GCC error is:

mwe.cpp:29:20: error: could not convert template argument '&A::foo' from 'void (A::*)(int)' to 'void (B::*)(int)'
like image 371
Joseph Avatar asked Jul 17 '20 09:07

Joseph


1 Answers

This is interesting.

Per the current rules*, it appears that the intent is for foo to remain a member of the base, rather than introducing an actual member of B.

That's despite the fact that overload resolution can now find the member in B:

[namespace.udecl/15]: [Note: For the purpose of forming a set of candidates during overload resolution, the functions that are introduced by a using-declaration into a derived class are treated as though they were members of the derived class ([class.member.lookup]). In particular, the implicit object parameter is treated as if it were a reference to the derived class rather than to the base class ([over.match.funcs]). This has no effect on the type of the function, and in all other respects the function remains a member of the base class. — end note]

That's also despite the fact that, in code, B::bar can refer to that member (i.e. it doesn't have to be spelled A::bar):

[expr.prim.id.qual/2]: A nested-name-specifier that denotes a class, optionally followed by the keyword template ([temp.names]), and then followed by the name of a member of either that class ([class.mem]) or one of its base classes, is a qualified-id; [class.qual] describes name lookup for class members that appear in qualified-ids. The result is the member. The type of the result is the type of the member. [..]

But the actual type of the member is therefore void (A::*)(int).

There is no rule permitting conversion to void (B::*)(int), even one specific to members introduced in this manner (and obviously such a conversion couldn't be valid in general).

Therefore, I believe that Visual Studio is in error.

* I'm citing the current draft, for convenience, but have no reason to believe that this rule has changed recently; both GCC and Clang reject the code in all of C++11, C++14 and C++17.


As an aside, this doesn't actually compile with the latest version of Visual Studio, either:

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

So, perhaps they've fixed the bug since your version. There is also a compatibility mode in VS that may be to blame.

like image 171
Asteroids With Wings Avatar answered Sep 30 '22 01:09

Asteroids With Wings