Compiler discrepancy for out-of-line definitions of conversion operators - c++

There appears to be some discrepancy between compilers as to whether the following code is well formed.
In particular, GCC and Clang accept this code -- whereas MSVC rejects it:
template <typename T>
class Bar{};
template <typename T>
struct Foo
{
using element_type = T;
operator Bar<element_type>();
};
template <typename T>
Foo<T>::operator Bar<element_type>()
{
return {};
}
MSVC's rejection message is just a generic:
<source>(11): error C2065: 'element_type': undeclared identifier
The issue only appears to occur when the template argument for the conversion type is a dependent template type, since this is resolved by changing Bar<element_type> to either Bar<T> or Bar<typename Foo<T>::element_type>. I've made a small example on compiler explorer to demonstrate this. This appears to occur from C++11 (possibly older, didn't test) up to C++2a, and is independent of the compiler's versions or flags.
I know that C++ allows dropping the prefixing class-name specifier for types when in the body or parameter list of a class function/constructor definition -- but I'm unsure whether this same short-form is allowed when handling a conversion operator in an out-of-line definition.
Is this an ambiguity in the standard, or a bug in either of the compilers? I'm mostly curious to know how this is defined (or not, as it may be) in the C++ standard.
Edit: To add to the confusion, it appears that MSVC accepts the following code:
template <typename T>
struct Foo
{
using element_type = T;
operator element_type();
};
template <typename T>
Foo<T>::operator element_type() // note: no 'typename' syntax
{
return {};
}
so this doesn't appear to be just an issue caused from conversion to template-dependent type names... I'm suspecting this may be an MSVC bug rather than a discrepancy.

The standard is very vague on the subject. In the case where you're explicitly calling a conversion function (a.operator int()), [basic.lookup.classref]/7 says
If the id-expression is a conversion-function-id, its conversion-type-id is first looked up in the class of the object expression ([class.member.lookup]) and the name, if found, is used.
Unfortunately, a conversion-type-id can have any number of names (including 0), in which case it is impossible to look up directly. [class.qual]/1.2 applies the same non-rule to references to the function using ::, which may or may not apply to out-of-class definitions of such functions (since normal lookup doesn't apply there).
There is massive implementation divergence. In the following example, the comments indicate which compilers (the most current versions at the moment on Compiler Explorer) reject which lines (usually because they can't find the name):
struct X {
struct A {};
operator A();
template<class> struct B {};
using C = int;
operator B<C>();
struct D {using I=int; I i;};
operator int D::*();
operator D::I();
static float E;
operator decltype(E)();
struct G {};
operator struct G();
};
X::operator A() {throw;} // OK
X::operator B<C>() {throw;} // MSVC: B and C
X::operator int D::*() {throw;} // MSVC
X::operator D::I() {throw;} // MSVC
X::operator decltype(E)() {throw;} // MSVC
X::operator struct G() {throw;} // MSVC thinks G is ::G
void f(X x) {
x.operator A(); // Clang, MSVC
x.operator B<C>(); // GCC, Clang, ICC: C; MSVC: B and C
x.operator int D::*(); // Clang
x.operator D::I(); // Clang
x.operator decltype(E)(); // Clang, ICC, MSVC; GCC segfaults
x.operator struct G(); // GCC, Clang, MSVC think G is local to f
}
Clang accepts all the definitions and rejects all the calls because it doesn't implement [basic.lookup.classref]/7 at all but helpfully misapplies [basic.scope.class]/4 within the declarator-id when it is a conversion-function-id.
New rules are in process to clarify this mess (along with many other messes); exactly how many of the above should be accepted is currently being discussed as part of reviewing that paper. My opinion is that GCC's behavior is closest to what is desired (aside from the ICE, of course).

Related

C++: MSC not resolving a template operator (gcc and clang ok)

I have a question about a C++ operator overloading on template class and type which is not correctly resolved by Microsoft Visual C++ while it is accepted by gcc and clang (Linux and macOS).
I suspect that this is a bug in MSVC++ but I would like to get the advice of experts before reporting the bug, in case it could be the opposite (gcc and clang being wrong).
The idea is a template class which must be instantiated with some integer type. We want to define the addition with any other plain integer types, both ways (class + int and int + class).
The minimal code for which I can reproduce the issue is below:
#include <type_traits>
template <typename INT, typename std::enable_if<std::is_integral<INT>::value>::type* = nullptr>
class A
{
public:
template<typename INT2>
A operator+(INT2 x) const { return A(); }
};
template <typename INT1, typename INT2>
A<INT2> operator+(INT1 x1, A<INT2> x2) { return x2 + x1; }
int main(int argc, char* argv[])
{
typedef A<int> B;
B x, y;
y = x + 1; // ok everywhere
y = 1 + x; // VC++: error C2677: binary '+': no global operator found which takes type 'B' (or there is no acceptable conversion)
}
In the original code, there are SFINAE constructs everywhere to enforce type checking when necessary (including in the two "+" operators). I have removed all of them when they did not change the compilation error. Only the "enable_if" in the definition of class A is required. Without it, the code compiles ok with MSVC++.
Microsoft Visual Studio 2019, version 16.9.3.
What is wrong here? MSVC++ or gcc/clang?
Thanks for your advices.
If we're being pedantic, in C++17, non-type template arguments cannot have type void*, see [temp.param]/4.2:
A non-type template-parameter shall have one of the following (optionally cv-qualified) types:
. . .
— pointer to object or pointer to function,
. . .
(Note: it has been rectified in C++20).
So it seems something goes wrong in MSVC (the SFINAE succeeds in A itself but causes the lookup of the operator to fail). MSVC doesn't support C++20 fully yet, but it may still be worth reporting the issue.
For more portable code, use SFINAE based on int instead of void*:
template <typename INT, typename std::enable_if<std::is_integral<INT>::value, int>::type = 0>
class A {
. . .
This compiles OK in MSVC 16.9.

Using incomplete type in a member function of a class template

GCC (8.3, 9.1), Clang (7, 8) and MSVC (19.20) differ is their ability to compile this code:
struct C;
template<typename T> struct S {
void foo() {
// C2 c;
C c;
}
};
class C {};
int main() {
S<int> s;
s.foo();
return 0;
}
GCC and MSVC accept it, whereas Clang rejects it. Clang rejects it even if I make foo itself a template and/or do not call it at all.
My understanding is that foo is not instantiated unless it is called, and it is instantiated at the point where it is called. At that point C is complete, and the code should compile. Is this a reasoning of GCC?
As a side note, if foo is not called, MSVC accepts the code even if I replace C with an undeclared C2 inside foo - in this case it seems to just check the function body to be syntactically correct.
Which behaviour is correct according to the Standard? If it is Clang's, why does the Standard forbids the flexibility that GCC gives?
This is ill-formed, no diagnostic required, due to [temp.res]/8:
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 [...]
[...]
So the whole program is bad, but implementations aren't required to diagnose it. Clang does, which is good for clang, gcc and MSVC don't, which isn't wrong.

Converting a pointer-to-member-of-base to a pointer-to-member-of-derived

A simplified example from a recent blog post:
struct B { void f(); };
struct D : B { };
constexpr auto as_d = static_cast<void(D::*)()>(&D::f); // (1)
template <void (D::*)()>
struct X { };
X<as_d> x; // (2)
gcc, clang, and MSVC all accept the declaration of as_d marked (1). gcc and clang both reject the declaration of x marked (2), but MSVC accepts it.
Both gcc's and clang's error messages indicate that they know that as_d is a pointer to member of B. clang:
<source>:9:3: error: sorry, non-type template argument of pointer-to-member type void (D::*)() that refers to member B::f of a different class is not supported yet
gcc:
<source>:9:7: error: void (D::*)(){((void (D::*)())B::f), 0} is not a valid template argument for type void (D::*)()
Who is right? If gcc/clang, what is the rule we're running afoul of? It sure seems like as_d is a converted constant expression of type void (D::*)() to me...
Well, this ended up being pretty interesting. As far as the language is concerned, the program is valid - as_d does meet the requirement for being a valid non-type template argument (it is a converted constant expression of the right type).
However, the Itanium C++ ABI apparently does not specify a mangling for this situation (that is, having a non-type template argument whose type is a pointer-to-member-to-derived but whose value is a pointer-to-member-to-base). Compilers targeting that ABI (i.e. clang and gcc), as a result, can't accept this code. This explains why clang's error is "sorry, not yet" rather than "no, bad!"
On the other hand, other ABIs have no such mangling problem, and so MSVC and ICC are both able to compile the program just fine.

Point of evaluation of exception specification

Consider these code snippets:
Version (1)
void q() {}
class B {
void f() noexcept(noexcept(q())) {q(); }
decltype(&B::f) f2;
};
Version (2)
void q() {}
class B {
void f() noexcept(true) {q(); }
decltype(&B::f) f2;
};
Version (3)
void q() {}
class B {
void f() noexcept {q(); }
decltype(&B::f) f2;
};
All versions of GCC compile these code snippets without any error or warning (including trunk-version). All versions of Clang which support C++17 deny version (1) and (2), but not version (3), with the following error:
<source>:4:16: error: exception specification is not available until end of class definition
decltype(&B::f) f2;
^
Take into account that the standard defines noexcept as equivalent to noexcept(true) [except.spec]. Thus, version (2) and version(3) should be equivalent, which they are not for clang.
Thus, the following questions: At which point do exception specifications need to be evaluated according to C++17 standards? And, if some codes above are invalid, what is the rational behind?
Abstract background for those who are interested:
template <typename F>
struct result_type;
template<typename R, typename C, typename... Args>
struct result_type<R(C::*)(Args...)> {
using type = R;
}; // there may be other specializations ...
class B {
int f() noexcept(false) { return 3; }
typename result_type<decltype(&B::f)>::type a;
};
This code should be valid at least up to C++ 14, because noexcept was not part of the function type (for clang, it compiled up to version 3.9.1). For C++ 17, there is no way to do this.
This is a result of CWG 1330.
Basically, the class is considered to be complete within its noexcept-specifier (in the resolution of the defect above it's referred to as an exception-specification).
This puts us in a situation where:
void q() {}
class B {
void f() noexcept(noexcept(q())) {q(); }
// ~~~~~~~~~~~~~
// evaluated in the context of complete B
decltype(&B::f) f2;
//~~~~~~~~~~~~~~~
//cannot wait until B is complete to evaluate
};
We need to know decltype(&B::f) to process the declaration of B::f2, but in order to know that type, we need to know the what the noexcept-specifier of B::f is (because now that's part of the type system), but in order to do that, we need to evaluate the noexcept-specifier in the context of the complete B.
The program is ill-formed, clang is correct.
I will answer this question myself with references to all relevant resources.
Let me cite Richard Smith to argue why this is a defect, and why all of the examples are ill-formed, because exception specification are only parsed at the end of the class.
Clang is approximately correct to reject that code.
Per C++ DR1330, exception specifications are not parsed until we reach the end of the class (they can name class members that are declared later, and the class is complete within them), just like member function bodies, default member initializers, and default arguments.
Finally, there's C++ DR361 (still open 16 years after being filed, sadly), wherein the conclusion of CWG is that the program is ill-formed if you use a delay-parsed construct before the end of the class. But we don't have actual standards wording to back that up yet, just a standard that's unimplementable without a fix to that defect.
LLVM Bug 37559, but also see Google Groups, CWG 361, CWG 1330
Further, noexcept should in principal be completely equivalent to noexcept(true), as stated in [except.spec], and it is clearly wrong to accept noexcept, but deny noexcept(true).
Thus, the conclusion is that all examples are ill-formed, even though that is not the desired behavior.

May pointer to members circumvent the access level of a member?

Our infamous litb has an interesting article on how to circumvent the access check.
It is fully demonstrated by this simple code:
#include <iostream>
template<typename Tag, typename Tag::type M>
struct Rob {
friend typename Tag::type get(Tag) {
return M;
}
};
// use
struct A {
A(int a):a(a) { }
private:
int a;
};
// tag used to access A::a
struct A_f {
typedef int A::*type;
friend type get(A_f);
};
template struct Rob<A_f, &A::a>;
int main() {
A a(42);
std::cout << "proof: " << a.*get(A_f()) << std::endl;
}
Which compiles and runs (output 42) with gcc 4.3.4, gcc 4.5.1, gcc 4.7.0 (see user1131467's comment) and compiles with Clang 3.0 and Comeau C/C++ 4.3.10.1 in C++03 strict mode and MSVC 2005.
I was asked by Luchian on this answer in which I used it to justify that it was actually legal. I agree with Luchian that it is weird, however both Clang and Comeau are close contenders for the most "Standard" compilers available (much more so than MSVC by default)...
And I could not find anything in the drafts of the Standards I have available (n3337 being the last version I got my hands on).
So... can anyone actually justifies that it is legal or not ?
Yes, it's legal. The relevant text is at §14.7.2/12, talking about explicit template instantiation:
12 The usual access checking rules do not apply to names used to specify explicit instantiations. [ Note: In particular, the template arguments and names used in the function declarator (including parameter types, return types and exception specifications) may be private types or objects which would normally not be accessible and the template may be a member template or member function which would not normally be
accessible. — end note ]
Emhpasis mine.
The code is clearly illegal (and requires a compile time diagnostic).
In the line:
template struct Rob<A_f, &A::a>;
the expression A::a accesses a private member of A.
The standard is very clear about this: “Access control is applied
uniformly to all names, whether the names are referred to from
declarations or expressions.“ (§11/4, emphasis added). Since a is a private name in A, any reference to it outside of A is illegal.