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.
Related
In the following code there is an initialization of A<T> objects with template argument deduction using two forms distinct by the type of braces:
template<typename T>
struct A{ T x; };
int main() {
static_assert( A{1}.x == 1 ); //#1: ok in GCC and MSVC
static_assert( A(1).x == 1 ); //#2: ok in GCC only
}
The first way is accepted by both GCC and MSVC, while the second one is ok for GCC only while MSVC prints errors:
error C2641: cannot deduce template arguments for 'A'
error C2780: 'A<T> A(void)': expects 0 arguments - 1 provided
error C2784: 'A<T> A(A<T>)': could not deduce template argument for 'A<T>' from 'int'
Demo: https://gcc.godbolt.org/z/97G1acqPr
Is it a bug in MSVC?
This is a bug in MSVC.
The following papers were all introduced in C++20:
P0960R3: Allow initializing aggregates from a parenthesized list of values
P1975R0: Fixing the wording of parenthesized aggregate-initialization
P2131R0: Fixing CTAD for aggregates
Whilst MSVC lists them all as implemented in their Microsoft C/C++ language conformance by Visual Studio version pages, it seems whilst they have correctly implemented them in isolation
// OK (P0960R3, P1975R0)
struct A { int x; };
A a(1);
// OK (P2131R0)
template<typename T>
struct B { T x; };
B b{1};
// rejects-invalid (allowed by the union of the papers)
template<typename T>
struct C { T x; };
C c(1);
MSVC seems to have missed implementing the union of the papers. I have not, however, been able to find an open bug report.
These lines being well-formed relies on an aggregate deduction candidate, which provides a way for T to be deduced from aggregate intialization lists. This feature is indifferent to the syntax, so failing on either one but not the other is already inconsistent.
In MSVC's case, it's the combination of class template parameter deduction and parenthesized aggregate intialization that is causing the issue (the latter works fine in isolation). MSVC also fails to compile A a(1);, which is more obviously well-formed.
I ran across a problem upgrading templated array references to std::array where I had used this:
template<class T, int N>
void f(T(&a)[N]){/* do stuff */;}
This has worked in all compilers I have used so I just did this when changing to std::array
template<class T, int N>
void f(std::array<T,N>& a){/* do stuff */;}
And this worked fine on MSVC. But when I ran the code on other compilers it did not
The following code works in MSVC but not other compilers. See https://godbolt.org/z/d9cqWMeaM which fails to match because N must be std::size_t.
*This question indicates that SFINAE should apply. However, as noted in the comments and in spite of the that answer, SFINAE, typically applies to rejection of malformed declarations. This is a deduction failure.
Is MSVC's acceptance of this code a compiler bug? And is the T(&a)[N] where int N was used legal even though all compilers I've ever used had no problem at all?
This was somewhat annoying because some of the prior code used the fact N was signed in various places.
std::array size_type is specified as std::size_t.
GCC and Clang are right, template deduction should fail in this case, according to [temp.deduct.type]/18:
If P has a form that contains <i>, and if the type of i differs from the type of the corresponding template parameter of the template named by the enclosing simple-template-id, deduction fails.
. . .
[ Example:
template<int i> class A { /* ... */ };
template<short s> void f(A<s>);
void k1() {
A<1> a;
f(a); // error: deduction fails for conversion from int to short
f<1>(a); // OK
}
Note: in C++11 this rule was a little more human-readable, see here.
So technically MSVC's acceptance of the code is a bug. I would report it to the vendor (Help -> Send Feedback -> Report a Problem).
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).
It is my understanding that decltype is used to query the type of an objects/variables and so on.
From the examples present on wikipedia, such as the following:
int i;
decltype(i) x3; // type is int
I assumed I could do something like this:
class A
{
public:
int a, b;
};
template<typename T>
struct IsClass
{
enum { Yes = std::is_class<T>::value };
enum { No = !Yes };
};
std::vector<A> v;
auto it = v.begin();
IsClass<decltype(it)::value_type>::Yes
Because after all this line is legal:
IsClass<std::vector<A>::iterator::value_type>::Yes
Alas it wouldn't compile, citing the following: error C2039: 'value_type' : is not a member of 'global namespace''`
Any ideas as to why scope resolution was made to behave this way in presence of decltype?
P.S: If it makes any difference I'm using MSVC2012 (without the Nov CTP)
This is a known bug in the Visual C++ compiler. It has not yet been fixed as of the Visual C++ 2013 Preview. You can work around this issue using std::common_type:
IsClass<std::common_type<decltype(it)>::type::value_type>::Yes
^^^^^^^^^^^^^^^^^ ^^^^^^^
(std::common_type with a single template argument yields that argument type; it's the standardized C++11 equivalent of the identity template that has long been used in metaprogramming.)
You can find the public bug report on Microsoft Connect: Cannot use decltype before scope operator. If this issue is important to you, please consider upvoting that bug report.
It is my understanding that decltype is used to query the type of an objects/variables and so on.
From the examples present on wikipedia, such as the following:
int i;
decltype(i) x3; // type is int
I assumed I could do something like this:
class A
{
public:
int a, b;
};
template<typename T>
struct IsClass
{
enum { Yes = std::is_class<T>::value };
enum { No = !Yes };
};
std::vector<A> v;
auto it = v.begin();
IsClass<decltype(it)::value_type>::Yes
Because after all this line is legal:
IsClass<std::vector<A>::iterator::value_type>::Yes
Alas it wouldn't compile, citing the following: error C2039: 'value_type' : is not a member of 'global namespace''`
Any ideas as to why scope resolution was made to behave this way in presence of decltype?
P.S: If it makes any difference I'm using MSVC2012 (without the Nov CTP)
This is a known bug in the Visual C++ compiler. It has not yet been fixed as of the Visual C++ 2013 Preview. You can work around this issue using std::common_type:
IsClass<std::common_type<decltype(it)>::type::value_type>::Yes
^^^^^^^^^^^^^^^^^ ^^^^^^^
(std::common_type with a single template argument yields that argument type; it's the standardized C++11 equivalent of the identity template that has long been used in metaprogramming.)
You can find the public bug report on Microsoft Connect: Cannot use decltype before scope operator. If this issue is important to you, please consider upvoting that bug report.