The following simplified example compiles in gcc
and Visual Studio
, but fails in clang
!?
namespace N
{
struct A {};
template <typename T>
double operator+ (T a, double d) {return d;}
template <typename T>
double operator+ (double d, T a) {return d;}
}
void test()
{
N::A a;
double x;
double y = a + x;
double z = x + a;
}
As I see it, the templated operator+
in namespace N
should be found by ADL.
Why does clang
disagree ? Is it a bug in clang
or in the other compilers ?
Here is the compilation error from clang 3.5.1 (tested on coliru), I don't understand what is the problem here...
10 : error: overloaded 'operator+' must have at least one parameter of class or enumeration type
double operator+ (double d, T a) {return d;}
^
18 : note: in instantiation of function template specialization 'N::operator+' requested here
double y = a + x;
^
7 : error: overloaded 'operator+' must have at least one parameter of class or enumeration type
double operator+ (T a, double d) {return d;}
^
19 : note: in instantiation of function template specialization 'N::operator+' requested here
double z = x + a;
^
2 errors generated.
Compilation failed
The example is simplified from real life code, of course. The intention is that any class defined inside namespace N has an overloaded operator+ with a double.
This means C++ has the ability to provide the operators with a special meaning for a data type, this ability is known as operator overloading. For example, we can overload an operator '+' in a class like String so that we can concatenate two strings by just using +.
C does not support operator overloading at all. You can only implement operations as functions: Colour colour_add(Colour c1, Colour c2); Colour colour_substract(Colour c1, Colour c2); ... You could also switch to C++, but it may be overkill to do it just for the overloading.
Overloading Binary Operators Suppose that we wish to overload the binary operator == to compare two Point objects. We could do it as a member function or non-member function. To overload as a member function, the declaration is as follows: class Point { public: bool operator==(const Point & rhs) const; // p1.
You can redefine or overload the function of most built-in operators in C++. These operators can be overloaded globally or on a class-by-class basis. Overloaded operators are implemented as functions and can be member functions or global functions. An overloaded operator is called an operator function.
This is caused by two different CWG issues: CWG issue 2052 and CWG issue 1391.
First, CWG 1391. On encountering x + a
, the usual name lookup finds, among other overloads,
template <typename T> double operator+ (T, double);
Template argument deduction is performed by match T
to the type of the lhs of +
, which is double
, so this deduces T
to be double
. The second parameter's type contains no template parameter, so is not considered under current rules. To be sure, N::A
can't be converted to a double
, so the resulting specialization is not viable, but the current rules say that template argument deduction doesn't care about this; that will be handled in overload resolution.
The proposed resolution to CWG 1391, among other things, adds a new paragraph to the standard:
If deduction succeeds for all parameters that contain template-parameters that participate in template argument deduction, and all template arguments are explicitly specified, deduced, or obtained from default template arguments, remaining parameters are then compared with the corresponding arguments. For each remaining parameter
P
with a type that was non-dependent before substitution of any explicitly-specified template arguments, if the corresponding argument A cannot be implicitly converted toP
, deduction fails. [Note: Parameters with dependent types in which no template-parameters participate in template argument deduction, and parameters that became non-dependent due to substitution of explicitly-specified template arguments, will be checked during overload resolution. —end note]
In other words, if an argument (a
in our case) corresponding to a non-dependent parameter (double
) cannot be converted to the parameter's type, deduction would simply fail. So in our case, post-CWG1391 template argument deduction will fail for this overload, and everything would be well.
Clang implements the current rules, however, so deduction succeeds with T = double
, substitution occurs, and we encounter CWG 2052. Quoting the writeup from Richard Smith (a Clang dev):
In an example like
struct A { operator int(); }; template<typename T> T operator<<(T, int); void f(A a) { 1 << a; }
Template argument deduction succeeds for the operator template, producing the signature
operator<<(int,int)
. The resulting declaration is synthesized and added to the overload set, per 14.8.3 [temp.over] paragraph 1. However, this violates the requirement of 13.5 [over.oper] paragraph 6,An operator function shall either be a non-static member function or be a non-member function that has at least one parameter whose type is a class, a reference to a class, an enumeration, or a reference to an enumeration.
This is not a SFINAE context, so the program is ill-formed, rather than selecting the built-in operator.
In this case, there's no conversion, so the deduced operator+(double, double)
is actually not viable, but non-viable candidates are not eliminated until you have built the candidate set, and here building the candidate set is causing a hard error.
The proposed resolution to CWG 2052 will make this case SFINAE instead - also making the original code work. The problem is - Clang is implementing the current version of the standard here, too.
It might be complaining because T
might not be a class in that definition. And you are not allowed to redefine the standard operator+
for arithmetic types IIRC. In your example, there's nothing restricting T
to be N::A
for instance.
Adding typename = std::enable_if_t<std::is_class<T>{} || std::is_enum<T>{}>
seems to fix it. Visual Studio and GCC might be a bit more lax/lazy about this restriction.
namespace N
{
struct A {};
template <typename T, typename = std::enable_if_t<std::is_class<T>{} || std::is_enum<T>{}>>
double operator+ (T a, double d) {return d;}
template <typename T, typename = std::enable_if_t<std::is_class<T>{} || std::is_enum<T>{}>>
double operator+ (double d, T a) {return d;}
}
void test()
{
N::A a;
double x;
double y = a + x;
double z = x + a;
}
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