7

I have the following code that will not compile, complaining that += operator does not exist. += operator is here declared outside of class A.

template < typename _T >
class A {
public:
    operator _T () const { return 42 ; }
};

template <typename _T >
A< _T > & operator += ( A< _T > & l, _T r ) { return l ; }


int main() {
    A< int > e, f ;
    e += f ;
    return 0 ;
}

However, if I implement the operator inside class A, code compiles and works :

template < typename _T >
class A {
public:
    operator _T () const { return 42 ; }

    A< _T > & operator += ( _T r ) { return *this ; }
};


int main() {
    A< int > e, f ;
    e += f ;
    return 0 ;
}

What is the difference between these two codes? Aren't they supposed to be equivalent?

This was compiled with gcc 4.4.7-4.

NathanOliver
  • 171,901
  • 28
  • 288
  • 402
Hugus
  • 73
  • 3
  • 2
    This isn't the problem, but names that begin with an underscore followed by a capital letter (`_T`) are reserved for use by the implementation. Don't use them in your code. – Pete Becker Dec 20 '16 at 16:59

4 Answers4

6

The first example fails to compile because template argument deduction does not do any conversion. With

template <typename _T >
A< _T > & operator += ( A< _T > & l, _T r ) { return l ; }

Both l and r contribute to determining what _T is. When you do e += f then the compiler gets that _T must be int for l and it gets r must be A<int> since that is the type of f. Since they do not match it fails to compile.

In the second code there is no template argument deduction going on. The compiler knows what _T is from the instantiation of the class so all it needs to be able to do is convert what is passed to r to a _T.


I also suggest you get out of the habit of starting names with underscores. There are a slew of rules about them and if you violate them then your program has undefined behavior since they are reserved for the implementation. For more see What are the rules about using an underscore in a C++ identifier?

Community
  • 1
  • 1
NathanOliver
  • 171,901
  • 28
  • 288
  • 402
3

The snarky short answer is, you used _T in user code, so your entire program is ill formed and no diagnostic required; identifiers consisting of a _ followed by a capital letter are reserved to be used by the implementation.

In that sense both examples are exactly equivalent.

Ignoring that error, they are not identical.

The first is a non-member template += operator.

The second is a non-template member += of a template class that takes an implicit this as its first parameter.

These are very different things. template function pattern matching does not do conversions (other than to-base); methods of template types can convert.

In the second case, the non-template operator+= is able to convert its second argument to be of type _T. In the first case, the template operator+= won't try to convert while pattern matching types.

There is actually a 3rd possibility, which I often prefer:

template < class T >
struct A {
  operator T () const { return 42 ; }
  friend A& operator += ( A& l, T r ) { return l; (void)r; }
};

where we make a free += as a friend. This creates a non-template += that takes two arguments, and hence is more symmetric.

Such non-template friends can be found via ADL.

live example.

As an aside, they also differ because a pointer to one can be stored in A<_T>& (A<_T>::*)( _T ), the other like A<_T>& (*)(A<_T>&, _T). These are not identical, nor can they be converted-between.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
1

Aren't they supposed to be equivalent ?

No, because in the first example, _T is a template function parameter which gets deduced, while in the second example it is already known.

Imagine the second example being expanded by the compiler:

template <>
class A_int {
public:
    operator int () const { return 42 ; }

    A< int > & operator += ( int r ) { return *this ; }
};

g++ 7.0 gives a good error message explaining why the deduction fails:

deduced conflicting types for parameter '_T' ('int' and 'A<int>')

A possible solution/workaround is adding an extra template parameter:

template <typename _T, typename _U>
A< _T > & operator += ( A< _T > & l, _U r ) { return l ; }

wandbox example

Vittorio Romeo
  • 90,666
  • 33
  • 258
  • 416
0

It's a bit tricky. In your second case, your operator is a member function of the class template A, not a template itself. When you call e += f, a match is found as A<int>::operator += (int), which already exists inside A<int>. There is an implicit conversion from A<int> to int, so this overload is valid.

In your first case, the operator is a template: the compiler tries to instanciate it by deducing the argument _T from the call site alone. Template argument deduction does not consider user-defined conversions, so the deduction fails.

The solution is to prevent the second parameter from participating in deduction, by making use of a non-deducible context such as an additional indirection through a template:

template <class T>
struct NonDeduced_ { using type = T; }

template <class T>
using NonDeduced = typename NonDeduced_<T>::type;

template <typename _T >
A< _T > & operator += ( A< _T > & l, NonDeduced<_T> r ) { return l ; }

Then only the first parameter participates in deduction, which succeeds, then the deduced _T is used to see if the second parameter has viable conversions.

Quentin
  • 62,093
  • 7
  • 131
  • 191