5

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.

Kirill Kobelev
  • 10,252
  • 6
  • 30
  • 51
dats
  • 236
  • 1
  • 10
  • From that error, it looks like it is being found by ADL. Not sure what it's complaining about though – xcvr Jun 17 '15 at 17:47
  • Note that your test program has undefined behaviour, since `x` is not initialized. – Kerrek SB Jun 17 '15 at 18:53
  • @KerrekSB You *are* reproducing because you're using gcc while can compile this. [Clang cannot](http://coliru.stacked-crooked.com/a/bd4cfc7eb1cc02d7). – David G Jun 17 '15 at 19:11

2 Answers2

5

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.

T.C.
  • 133,968
  • 17
  • 288
  • 421
  • Why would `operator+(double, double)` be deduced at all in the OP's situation? – Kerrek SB Jun 17 '15 at 20:44
  • @KerrekSB Given `template double operator+ (T, double); double x; N::A a;` and `x + a`, `T` is deduced from the lhs, i.e., `double`. Of course there's no conversion from `N::A` to `double` for the rhs, but template argument deduction doesn't care; that's left for overload resolution. – T.C. Jun 17 '15 at 20:47
  • Interesting hole in the standard. Thanks for the explanation. I guess clang was right, and GCC/VC will be right. – xcvr Jun 18 '15 at 01:47
  • @T.C.: Oh, of course -- I was thinking of the other function. Yes, makes sense. This should have been a SFINAE context but isn't. I imagine the CWG issue will be a DR against 11 -- nobody will want this kind of behaviour to depend on the language version. – Kerrek SB Jun 18 '15 at 09:39
  • Thanks for the explanation. According to this answer, I only have two alternatives: a) provide an overloaded operator for each class in namespace N, or b) use CRTP in all classes in namespace N and then define the operators in terms of the common base class. Am I right? – dats Jun 18 '15 at 15:22
  • @dats No, the `enable_if` in the other answer is fine. It causes a deduction failure when `T` is deduced as `double`, so the substitution (to form the ill-formed `operator+(double, double)`) doesn't take place. – T.C. Jun 18 '15 at 16:23
3

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;
 }
xcvr
  • 939
  • 8
  • 15
  • I don't think that problem should be a concern until instantiation. For example, if you [remove one of the overloads](http://coliru.stacked-crooked.com/a/a361da7a886c1a26) clang won't complain. – David G Jun 17 '15 at 18:20
  • @0x499602D2 True. Something odd is happening here. If you leave in the other addition, http://coliru.stacked-crooked.com/a/c3dd251ee27581b3, you still get that error popping up. – xcvr Jun 17 '15 at 18:35
  • The error messages (both the original, and the coliru ones), indicate that it is instantiating the functions with `T = double` in those instances where it fails to compile. `in instantiation of function template specialization 'N::operator+' requested here` – xcvr Jun 17 '15 at 18:59
  • Yes, I think clang is inadvertantly instantiating the candidates while performing overload resolution. – David G Jun 17 '15 at 19:08
  • According to the answer by T.C I don't think enable_if will solve the problem in clang. – dats Jun 18 '15 at 15:18
  • @dats It definitely fixes the problem. http://coliru.stacked-crooked.com/a/f82b2eeed3600729 – xcvr Jun 18 '15 at 18:51