41

I have a template class A

template <unsigned int m>
class A
{
public:
    A(int) {}
};

Which has a constructor from int. And I have an operation:

template<unsigned int m>
A<m> operator+(const A<m>&, const A<m>&)
{
    return A<m>(0);
}

But when I call:

A<3> a(4);
A<3> b = a + 5;
A<3> c = 5 + a;

I would like int to be implicitly converted to A, but compilers throws error.

Is there any elegant way to enable implicit conversion without using such solutions as:

  • a + A<m>(5)
  • operator+<3>(a, 5)
Baum mit Augen
  • 49,044
  • 25
  • 144
  • 182
Seagull
  • 3,319
  • 2
  • 31
  • 37
  • The only way to get this to work is to provide an overload (of `operator+`) which accepts an `int`. – Nim Mar 20 '12 at 13:27
  • Here's a good answer to a previously asked question: http://stackoverflow.com/a/2833761/1257977 – George Skoptsov Mar 20 '12 at 13:28
  • 4
    In `a + 5`, what should `5` turn into, an `A<5>(5)` or an `A<3>(5)`? If the latter, where should the 3 come from? – Seth Carnegie Mar 20 '12 at 13:44
  • 1
    It should be converted to `A<3>(5)`. The `m` value in `+` is same for two arguments, so it should take it from the first argument. – Seagull Mar 20 '12 at 13:55
  • 1
    Knee-jerk reaction: use `explicit` on your constructors please. Implicit conversions should be reserved to very special cases... – Matthieu M. Mar 20 '12 at 14:03
  • 1
    But this is rare case when I should use implicit constructor. – Seagull Mar 20 '12 at 14:07

4 Answers4

47

The solution is already shown in this answer. Now, more about the problem...

The problem in your code is how overload resolution is performed. When a template function is considered for overload resolution the compiler will perform type deduction on the arguments and come up with a type substitution that matches the call or else it fails to apply that template, removes it from the set of potential candidates and continues over. The problem at this point is that type deduction only deduces exact matches (with possible extra const/volatile qualification). Because the matching is exact, the compiler will not use any conversion (again, other than cv).

The simplest example of this happens with std::max and std::min functions:

unsigned int i = 0;
std::min( i, 10 );    // Error! 

Type deduction will deduce the T in template <typename T> min( T const &, T const & ) to be unsigned for the first argument but int for the second they differ and the compiler will discard this template function.

The solution proposed in the answer is using a feature of the language that enables you to define a non-member friend function inside the class definition. The advantage with templates is that for every (different) instantiation of the template, the compiler will create a free non-template function at namespace level that has the signature obtained by substituting the real types of the instantiation in the friend declaration:

template <typename T>
class test {
    friend test operator+( test const & lhs, test const & rhs ) {  // [1]
        return test();
    }
}
test<int> t;                                                       // [2]

In the example above, the compiler allows you to add the definition of the friend function inside the class scope at [1]. Then when you instantiate the template in [2], the compiler will generate a free function:

test<int> operator+( test<int> const & lhs, test<int> const & rhs ) { 
   return test<int>();
}

The function is defined always, whether you use it or not (this differs to the template class member functions, that are instantiated on demand).

The magic here has multiple sides to it. The first part is that it generically you are defining non-template functions for each and all of the instantiated types, so you gain genericity and at the same time the advantage of overload resolution being able to use this function when the arguments are not perfect matches.

Because it is a non-template function, the compiler is able to call implicit conversions on both arguments, and you will get your expected behavior.

Additionally, a different type of magic goes on with lookup, as the function so defined can only be found by argument dependent lookup unless it is also declared at namespace level, which in our case cannot be done in a generic way. The implication of this might be good or bad, depending on how you want to consider it...

Because it can only be found by ADL it will not be considered unless at least one of the arguments is already of the desired type (i.e. it will never be used performing conversions to both arguments). The downside is that it is impossible to refer to the function unless you are actually calling it, and that means that you cannot obtain a function pointer.

(More on template friendship here, but note that in this particular case, all the other variants will fail to perform implicit conversions).

Community
  • 1
  • 1
David Rodríguez - dribeas
  • 204,818
  • 23
  • 294
  • 489
  • 1
    Is there a way to define this operator overload out of class and only have a "special" friend declaration in the class body? I haven't found a way to do so. – onitake Aug 20 '14 at 13:53
  • @onitake: I don't quite understand what you are asking – David Rodríguez - dribeas Aug 20 '14 at 15:52
  • 1
    @dribeas: I usually prefer separating declaration and implementation. So, if I want to only put the friend declaration into the class, but have the definition of the function elsewhere, is there a way to do this? – onitake Aug 21 '14 at 10:36
  • @onitake: Kind of late, but not even 3 years... You cannot define that friend function externally as it is a different function for each instantiation of the template. What you can do to avoid adding much code there is to provide a function that does the actual work (say `static test add(test const&, test const&)`) in the class and have the definition of the friend just forward: `test operator+(test const& a, test const& b) { return add(a,b); }`. Not perfect but at least the definition in the class is a single line. The more complex logic is hidden in `test::add`. – David Rodríguez - dribeas May 11 '17 at 09:40
  • @DavidRodríguez-dribeas This answer isn't quite correct. It says the match has to be exact "with possible extra const/volatile qualification". However, derived to base, as well as certain pointer qualification conversions are allowed too (e.g. `int *` to `int const *`). http://en.cppreference.com/w/cpp/language/template_argument_deduction. Happy to edit if you agree. – Nir Friedman Aug 22 '17 at 14:03
  • This gives a great understanding of the situation, thank you @DavidRodríguez-dribeas! Now, I am doing a bit of necromancy wondering if there is a solution able to also handle conversions on *both* arguments? (Or does it **require** to write the explicit overload with both arguments as `A`?) – Ad N Jul 22 '21 at 12:31
  • Answering to myself: it does work in my environment when both types are `A` (VS 16.10.13, compiling for C++17). – Ad N Jul 22 '21 at 12:37
23

Every attempt to provide an operator using templates will need at least one second overload. But you can avoid that by defining the operator inside the class:

template <unsigned int m>
class A
{
public:
  A(int) {}
  inline friend A operator+(const A& a, const A& b) { return A(0); }
};

Works for both, a+5 and 5+a.

Alexis Wilke
  • 19,179
  • 10
  • 84
  • 156
ipc
  • 8,045
  • 29
  • 33
1

Add this operator

template<unsigned int m>
A<m> operator+(const A<m>&, const int&)
{
    return A<m>(0);
}

OR try this

template <unsigned int m>
class A
{
friend const A operator+(const A& a, const A& b) { return A(0); }
public:
    A(int) {}
// OR FOR UNARY
    // const A operator+(const A &a) const {return A(0);}
};


int main(){
    A<3> a(4);
    A<3> b = a + 5;
    A<3> c = 5 + a;

}

Dmitriy Kachko
  • 2,804
  • 1
  • 19
  • 21
0

You could try adding an additional "policy" type argument to the template for your A class that will determine the actual desired conversion type. For instance:

template <unsigned int m, typename ConvVal = int>
class A
{
        public:
                typedef ConvVal conv_val;

                A(ConvVal) {}
};

template<template <unsigned int, class U> class T, unsigned int m, typename U>
T<m, U> operator+(const T<m, U>&, const T<m, U>&)
{
        return T<m, U>(0);
}

template<template <unsigned int, class U> class T, unsigned int m, typename U>
T<m, U> operator+(const T<m, U>&, const typename T<m, U>::conv_val&)
{
        return T<m, U>(0);
}

template<template <unsigned int, class U> class T, unsigned int m, typename U>
T<m, U> operator+(const typename T<m, U>::conv_val&, const T<m, U>&)
{
        return T<m, U>(0);
}

int main()
{
        A<3> a(4);
        A<3> b = a + 5;

        return 0;
}

Now your A class will take an additional template argument that defaults to an int type, and it defines the actual type that you will allow automatic conversions from. You only need to overload the operator+ function three times, once for the version without the conversion value that will take explicit classes of type A<m, T>, and another for the two versions of operator+ that will take conversion types. In the above code I've generalized this with more generic types so that this can be done with pretty much any other class that has the proper template signature, and defines a conv_val typedef.

Jason
  • 31,834
  • 7
  • 59
  • 78