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 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.
Related
In C++ one cannot overload in one class a member function with ref-qualifier with a member function without ref-qualifier. But at the same time it is possible to inherit one member function from a parent class and overload it in a child class as in the example:
struct A {
void f() {}
//void f() & {} //overload error everywhere
};
struct B : A {
using A::f;
void f() & {} //ok everywhere
};
int main() {
B b;
b.f(); //ok in GCC only
}
Only during the invocation of f, Clang complains that call to member function 'f' is ambiguous. But GCC accepts the program without any error, demo: https://gcc.godbolt.org/z/5zzbWcs97
Which compiler is right here?
GCC is correct to accept this, but the situation changed recently. The current phrasing is that a using-declaration in a class ignores (base-class) declarations that would be ambiguous (in a sense that is more strict than for overload resolution, partly because there is no argument list yet) with other declarations in the class. void() and void() & members are ambiguous in this sense, so b.f finds only B’s f and the call is valid.
In previous (as of this writing, that means “published”) versions of the standard, both functions would be made available because the & distinguished them (in a sense that is even stricter), which would not only render the call ambiguous (as Clang says) but be ill-formed outright because the base- and derived-class functions were checked for overload compatibility which they lack.
The following code compiles and works on clang, but fails with "error: invalid use of non-static data member ‘Outer::a’" on gcc:
#include <functional>
#include <vector>
#include <assert.h>
#include <iostream>
#include <memory>
class Outer
{
public:
bool a = false;
virtual void f() = 0;
template <typename T>
class Inner : public T
{
public:
virtual void f() override
{
a = true; // Note: accessed through inheritance, not through outer scope
}
};
};
struct Foo : Outer { };
int main()
{
Outer::Inner<Foo> f;
f.f();
}
Adding "this->a" to the inner class makes it work on both compilers, but I'm wondering what's the correct behavior and what the standards says about this.
Interestingly the above code works with as part of a larger code base in VS2017 at work, but when I try it at home with VS2017 in isolation, it fails with the same error as GCC.
You can try compiling it here:
clang: https://rextester.com/SKAUEY50097
gcc: https://rextester.com/FLGL37556
This code is ill-formed no diagnostic required. So Gcc is right and friendly. And the absence of diagnostic for Clang and MSVC is just a compiler quality issue.
The rule of the standard involved is [temp.res]/8:
The validity of a template may be checked prior to any instantiation.
[ Note: Knowing which names are type names allows the syntax of every template to be checked in this way.
— end note
]
The program is ill-formed, no diagnostic required, if:
[..]
a hypothetical instantiation of a template immediately following its definition would be ill-formed due to a construct that does not depend on a template parameter, or [...]
In f body, the unqualified id-expression a does not depend on any template parameter, so this id-expression should be resolved at the point of definition of the template without the knowledge of any template argument. And at this point, this expression is ill-formed.
Note: a non qualified id-expression, (out of a class member access) expression is supposed to be a member only if it names a member of that class or of a non-dependent base [temp.dep.type]/5:
A name is a member of the current instantiation if it is:
An unqualified name that, when looked up, refers to at least one member of a class that is the current instantiation or a non-dependent base class thereof.
I stumbled upon this weird name lookup issue, where a base class member function doesn't seem to participate in the overload selection at all, even though it is imported with a using statement. The member functions of the base and derived classes are both SFINAE'd with enable_if_t.
I was able to reproduce my issue with the following code: https://gcc.godbolt.org/z/ueQ-kY
#include <iostream>
#include <type_traits>
class MyTag {};
struct Base
{
template <typename RType>
std::enable_if_t<std::is_convertible<RType, int>::value> create(RType /*&&*/ ref)
{
std::cout << "Base::create(RType ref)" << std::endl;
}
};
struct Derived : Base
{
using Base::create;
template <typename Tag>
std::enable_if_t<std::is_same<Tag, MyTag>::value> create(Tag /*&&*/ tag)
{
std::cout << "Derived::create(Tag tag)" << std::endl;
}
};
int main()
{
Derived d;
d.create(MyTag());
d.create(0); // [x86-64 clang 7.0.0 #1] error: no matching member function for call to 'create'
}
While GCC compiles the above code without warnings, clang, icc, and MSVC aren't able to find a suitable overload for the call of d.create(0); and error the build. In fact, judging from the error messages, it seems like Base::create isn't even taking part in the overload resolution.
However, when one of the two member function takes its argument as a forwarding reference, the code compiles fine on all major compilers.
Gcc is wrong and should reject your example.
using-declaration:
using using-declarator-list ;
[namespace.udecl]/1
Each using-declarator in a using-declaration introduces a set of declarations into the declarative region in which the using-declaration appears. The set of declarations introduced by the using-declarator is found by performing qualified name lookup ([basic.lookup.qual], [class.member.lookup]) for the name in the using-declarator, excluding functions that are hidden as described below.
The excluded functions being:
[namespace.udecl]/15
When a using-declarator brings declarations from a base class into a derived class, member functions and member function templates in the derived class override and/or hide member functions and member function templates with the same name, parameter-type-list, cv-qualification, and ref-qualifier (if any) in a base class (rather than conflicting). Such hidden or overridden declarations are excluded from the set of declarations introduced by the using-declarator.
However, when one of the two member function takes its argument as a universal reference, the code compiles fine on all major compilers.
When one of the function takes its argument as a (forwarding) reference, this template function doesn't qualify anymore as hidden since its parameter-type-list differs.
A bug report has been opened by OP, check it out:
Bug 87478 - Hidden member function falsely takes part in qualified name lookup.
For example, the following code piece compiles with gcc-4.9 and clang-602
class Base
{
public:
static void foo() {}
void badfoo(int i) {}
};
template <typename T>
class Derived : public Base
{
public:
void bar() { Base::foo(); }
void badbar() { Base::badfoo(); } // compiles ok
//static void badbar() { Base::badfoo(); } // compile error
//void worsebar() { Base::nonexist(); } // compile error
};
int main()
{
return 0;
}
But the commented out lines won't compile.
My questions are:
Why badbar() compiles but worsebar() doesn't ?
If I change badbar() to static it won't compile either, regardless if base::badfoo is static or not.
Does the standard say anything about what should be checked in this situation ? gcc4.4 actually refuses to compile even badbar().
Update:
Problem 1 has been explained by a number of answers, but it seems compilers sometimes go the extra mile to check overload as well, it happens to gcc 4.4.3 and 4.8.2, but not 4.7.2 and 4.9.1.
Problem 2: As Marco A. pointed out, clang won't compile but gcc4.9 will still pass. However, gcc4.2 and gcc4.4 both reject the code, and the error they are complaining is "no matching function" rather than "calling non-static member without an object" in clang. There's seems to be no conclusive answer to this question, so I'm adding a language-lawyer tag as Daniel Frey suggested.
More Update:
I think for question 2 the answer is pretty clear now: it's up to the compiler whether adding static declaration will change the diagnosis. It varies from compiler to compiler and different versions of the same compiler. The language lawyer didn't show up, I'm going to accept Daniel Frey's answer as it best explained the first question. But answers from Marco A. and Hadi Brais also worth reading.
The standard only requires the name-lookup to happen at phase 1, when the template is first parsed. This turns up badfoo in badbar which is why the code compiles. The compiler is not required to do the overload resolution at that time.
The overload resolution (which always happens as a separate step after the name lookup) is then performed in phase 2 when badbar is instantiated - which is not the case in your example. This principle can be found in
3.4 Name lookup [basic.lookup]
1 The name lookup rules apply uniformly to all names (including typedef-names (7.1.3), namespace-names (7.3), and class-names (9.1)) wherever the grammar allows such names in the context discussed by a particular rule. Name lookup associates the use of a name with a declaration (3.1) of that name. Name lookup shall find an unambiguous declaration for the name (see 10.2). Name lookup may associate more than one declaration with a name if it finds the name to be a function name; the declarations are said to form a set of overloaded functions (13.1). Overload resolution (13.3) takes place after name lookup has succeeded. The access rules (Clause 11) are considered only once name lookup and function overload resolution (if applicable) have succeeded. Only after name lookup, function overload resolution (if applicable) and access checking have succeeded are the attributes introduced by the name’s declaration used further in expression processing (Clause 5).
(Emphasis mine)
I'd therefore say that the compiler(s) are correct to accept the code, although I'm not sure that they are required to do so.
To see the code being rejected, you need instantiate badbar.
Consider [temp.res]/8:
If no valid specialization can be generated for a template, and that
template is not instantiated, the template is ill-formed, no
diagnostic required.
This (in particular the "no diagnostic required" bit) makes any compiler's behaviour compliant with respect to worsebar. Implementations' discrepancies on this kind of code are just QoI issues - common compilers do some analysis and will complain. It is hard to say when exactly, and you should be prepared to come back to template code when upgrading or switching your implementation.
To make some clarity.. this version of the code compiles just fine on clang and gcc
class Base
{
public:
static void foo() {}
void badfoo(int a) {}
};
template <typename T>
class Derived : public Base
{
public:
void bar() { Base::foo(); }
void badbar() { Base::badfoo(); }
};
since
[temp.res]/p8
If no valid specialization can
be generated for a template, and that template is not instantiated, the template is ill-formed, no diagnostic
required.
Both gcc and clang aren't required to diagnose this. This one also falls in the same case as above (clang emits an error, gcc doesn't)
class Base
{
public:
static void foo() {}
void badfoo(int a) {}
};
template <typename T>
class Derived : public Base
{
public:
void bar() { Base::foo(); }
static void badbar() { Base::badfoo(); }
};
The case with
void worsebar() { Base::nonexist(); }
is different since it violates name lookup [temp.res]/p9
When looking for the declaration of a name used in a template definition, the usual lookup rules (3.4.1,
3.4.2) are used for non-dependent names
and [temp.res]/p10
If a name does not depend on a template-parameter (as defined in 14.6.2), a declaration (or set of declarations)
for that name shall be in scope at the point where the name appears in the template definition
Disclaimer: all of the above does not apply to MSVC which happily postpones all this stuff to the second phase of the lookup.
Edit:
in the case
class Base
{
public:
static void foo() {}
void badfoo(int i) {}
};
template <typename T>
class Derived : public Base
{
public:
static void badbar() { Base::badfoo(); } // static function
clang triggers an error while gcc doesn't. This falls in the first case since name lookup is successful but clang performs an additional check: since badfoo is a member function it tries to construct a valid implicit member reference expression. It then catches the fact that a member function is implicitly being called from a static function and detects the context mismatch. This diagnostic is entirely up to the compiler at this point (it wouldn't in case of an instantiation).
Before it instantiates any types or emits any code, the compiler incrementally builds a table of all symbols that have been declared. If an undeclared symbol has been used, it emits an error. That's why worsebar won't compile. On the other hand, badfoo has been declared and so badbar compiles. At this early point in the compilation process, the compiler won't check whether the call to badfoo actually matches the declared badfoo.
Since the Derived type has not been instantiated anywhere in the code, the compiler will not emit any code regarding it. In particular, badbar will just be neglected.
Now when you declare an instance of Derived (such as Derived< int >) but without using any of its members, the compiler will just create a type with those members that have been used and omit the others. Still, no error regarding badbar.
However, when declaring an instance of Derived and calling badbar, an instantiation of the badbar method would be required and so the compiler will create a type with badbar and compile it. This time, the compiler notices that badfoo is not actually declared and therefore emits an error.
This behavior is documented in the C++ standard in section 14.7.1.
Unless a member of a class template or a member template has been explicitly instantiated or explicitly specialized, the specialization of the member is implicitly instantiated when the specialization is referenced in a context that requires the member definition to exist.
Finally, if badbar was static and was instantiated by the compiler (because it has been used) then the compiler will emit an error that badfoo does not exist. Now if you pass an integer argument to badfoo, another error will be emitted indicating that a static method cannot access an instance member because there is no instance in the first place.
Edit
The compiler is not obliged to NOT report semantic errors in uninstantiated template types. The standard just says that it does not have to, but it can. Regarding where to draw the line is open for debate. See this discussion about a related issue in clang:
which uninstantiated templates do we analyze? For performance reasons, I don't think we should analyze all the uninstantiated templates, as we may find ourselves repeatedly analyzing a huge portion of the Boost and the STL, etc.
So uninstantiated templates analysis changes with different versions of clang and gcc in different ways. But again, as per the standard: There's no requirement to report errors in uninstantiated templates, of course.
At first if you would instantiate Devired and try to call badbar there would be a compilation error:
// ...
int main()
{
Derived<int> d;
d.badbar();
return 0;
}
produces
error: too few arguments to function call, single argument 'i' was not specified
void badbar() { Base::badfoo(); } // compiles ok
~~~~~~~~~~~~ ^
The compiler doesn't compile the code, that is not instantiated.
I try to compile the following code with clang (version 3.0), but it gives me this error
error: no matching member function for call to 'a'
in the call __a.a<0>(). Then I try with g++ (version 4.2.1) and it compiles and works as expected (print out 1 2).
#include <iostream>
struct A {
template <int> int a() { return 1; }
};
struct B: A {
using A::a;
template <int,int> int a() { return 2; }
};
int main(int, char **) {
B __a;
std::cout << __a.a<0>() << " " << __a.a<0,0>() << std::endl;
return 0;
}
I try to look to the standard but I have not found anything that explains which is the correct behavior of compiler. Now, my question is which is the correct behavior, and if clang works correctly, how I can modify my code to work as expected?
Digging through both the C++03 and C++11 standard, it doesn't look good for your code to be valid and well-formed. C++03 seems to have allowed this, while a change in the wording of the C++11 standard seems to have disallowed this.
§7.3.3 [namespace.udecl] (Both standards)
p12 (C++03) When a using-declaration brings names from a base class into a derived class scope, member functions in the derived class override and/or hide member functions with the same name and parameter types in a base class (rather than conflicting).
Note that this wording doesn't mention any member function templates.
p15 (C++11) When a using-declaration brings names from a base class into a derived class scope, member functions and member function templates in the derived class override and/or hide member functions and member function templates with the same name, parameter-type-list (8.3.5), cv-qualification, and ref-qualifier (if any) in a base class (rather than conflicting).
Note the mentioning of member function templates in the new wording. Also note, that the list that determines if a derived class member overrides / hides a base class member doesn't mention the template-parameter-list of the member function template as an identifying point, it is being ignored for this purpose.
I might be interpreting this completely wrong, but it seems Clang is the conforming compiler here, and GCC aswell as MSVC10 are non-conforming according to the new wording.