C++ operator overloading and associated namespace

1.3k views Asked by At

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.

2

There are 2 answers

6
T.C. On BEST ANSWER

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 to P, 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.

6
xcvr On

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;
 }