Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ template, ambiguous overload

for some reason I have two classes implementing the operator "+" with templates, (I do that because I want all children of those two classes to be able to use it).

I have come down to a very simple code implementing what I would like to use:

#include <type_traits>

class A{};

template<typename T>
A operator+(T& lhs,int rhs){
  static_assert(std::is_base_of<A, T>::value, "T must inherit from A");
  A to_return;
  return to_return;
}

class B{};

template<typename T>
B operator+(T& lhs,int rhs){
  static_assert(std::is_base_of<B, T>::value, "T must inherit from B");
  B to_return;
  return to_return;
}


int main()
{
  A u;
  A v = u+1;
}

When compiling, the compiler (g++ or intel) returns the following error :

  • g++ : main.cpp:25:11: error: ambiguous overload for 'operator+' in 'u + 1' main.cpp:25:11: note: candidates are: main.cpp:6:3: note: A operator+(T&, int) [with T = A] main.cpp:15:3: note: B operator+(T&, int) [with T = A]

  • icpc : main.cpp(25): error: more than one operator "+" matches these operands: function template "A operator+(T &, int)" function template "B operator+(T &, int)" operand types are: A + int A v = u+1; ^

Although it is not that ambiguous as v should be of type A, so only the first template should work.

Any idea to get around this keeping the two templates operators ?

Or another idea to have an operator working for all children of A and B ? I.e. for all classes C child of A, I would like to be able to write A w = u + 1; //where u is of type C. And same for B.

Thank you,

Tony

EDIT:

Following the answer given by Barry, std::enable_if does the job. However, it turns out that what I needed exactly was to use two typenames, the technique proposed by Barry has to be slightly modified to add this possibility:

#include <type_traits>
#include <iostream>

class A{};

template<typename T1,typename T2 = typename std::enable_if<std::is_base_of<A,T1>::value, A>::type>
A operator+(T1& lhs,T2& rhs){
  A to_return;
  return to_return;
}

class B{};

template<typename T1,typename T2 = typename std::enable_if<std::is_base_of<B,T1>::value, B>::type>
B operator+(T2& lhs,T2& rhs){
  B to_return;
  return to_return;
}


int main()
{
  A u;
  A w = u+u;
}

Then it works fine, even if T1 and T2 are different children of A.

like image 841
Tony Avatar asked Jan 10 '23 10:01

Tony


1 Answers

Overload resolution is solely based on the function signature, which is its name, its cv-qualifications, and its parameter types.

For your first one, that is:

operator+(T& lhs, int rhs);

And for your second one, that is also:

operator+(T& lhs, int rhs);

Since those are identical, the compiler can't distinguish between the two - hence the ambiguity. One way around this is to move your static assert into the return type and use SFINAE:

template<typename T>
typename std::enable_if<
    std::is_base_of<A, T>::value,
    A
>::type
operator+(T& lhs,int rhs){
    // stuff
}

And the same for your other operator. This will work until you try it with some T that derives from both, and then it will become ambiguous again.

Or, depending on what you're actually doing with lhs, simply:

A operator+(A& lhs, int rhs); // already accepts anything that derives from A
like image 70
Barry Avatar answered Jan 19 '23 11:01

Barry