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)'
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 keywordtemplate
([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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With