QuantumNik QuantumNik - 3 months ago 20
C++ Question

template argument deduction/substitution in operator

I have a set of classes derived from one base:

template<typename Traits>
class Base{
typedef typename Traits::scalar_t scalar_t;
public:
virtual ~Base(){}
virtual scalar_t apply(const scalar_t&) const=0;
};

template<typename Traits>
class Pover_of_Number:
public Base<Traits>{
typedef typename Traits::scalar_t scalar_t;
public:
Pover_of_Number(const scalar_t& power):
power(power){}
scalar_t apply(const scalar_t& value) const override{
return pow(value,power);
}
private:
scalar_t power;
};

template<typename Traits>
class Mult_by_Number:
public Base<Traits>{
typedef typename Traits::scalar_t scalar_t;
public:
Mult_by_Number(const scalar_t& num):
num(num){}

scalar_t apply(const scalar_t& value) const override{
return num*value;
}
private:
scalar_t num;
};

template<typename Traits>
class SumOperator:
public Base<Traits>{
typedef Base<Traits> BT;
typedef typename Traits::scalar_t scalar_t;
public:
SumOperator(const std::shared_ptr<Base<Traits>>& op1,
const std::shared_ptr<Base<Traits>>& op2):
op1{op1}, op2{op2}{}

scalar_t apply(const scalar_t& value) const override{
return op1->apply(value)+op2->apply(value);
}
private:
std::shared_ptr<BT> op1;
std::shared_ptr<BT> op2;
};


With some particular
Traits
this code works perfectly:

class TraitsExample{
public:
typedef double scalar_t;
};

int main(){
auto op1=std::make_shared<Pover_of_Number<TraitsExample>>(2.);
auto op2=std::make_shared<Mult_by_Number<TraitsExample>>(10.);

//works correctly
auto sumop=std::make_shared<SumOperator<TraitsExample>>(op1,op2);
std::cout<<sumop->apply(2.)<<std::endl;


Now I want to overload
operator+
which used my derived classes (3=1+2):

template<typename Traits>
std::shared_ptr<SumOperator<Traits>> operator+(
const std::shared_ptr<Base<Traits>>& op1,
const std::shared_ptr<Base<Traits>>& op2){
return std::make_shared<SumOperator<Traits>>(op1,op2);
}


However when I use it in the code I have a
template argument deduction/substitution failed
error.

Could someone explain to me what exactly needs to be done in order for gcc to compile my code, preferably without using any additional classes for deduction.

PS: One can find working example. As I said I need additionally to overload
+
operator.

Answer

The trouble here is that the compiler can't deduce Traits from trying to match argument type std::shared_ptr<Pover_of_Number<TraitsExample>> against parameter type std::shared_ptr<Base<Traits>>. There's an implicit conversion involved in converting from the first to std::shared_ptr<Base<TraitsExample>>, so the deduction can't happen automatically.

I would add a public typedef to your Base<Traits> class and use that to figure out what the return type of operator+ should be:

#include <type_traits>

template<typename Traits>
class Base{
  typedef typename Traits::scalar_t scalar_t;
public:
  using traits_type = Traits;
  virtual ~Base(){}
  virtual scalar_t apply(const scalar_t&) const=0;
};

template<typename T, typename U>
std::enable_if_t<std::is_same<typename T::traits_type, typename U::traits_type>::value,
                 std::shared_ptr<SumOperator<typename T::traits_type>>> operator+(
  const std::shared_ptr<T>& op1,
  const std::shared_ptr<U>& op2){
  return std::make_shared<SumOperator<typename T::traits_type>>(op1,op2);
}

Note that due to SFINAE, this operator+ won't incorrectly match anything without a traits_type member.

Full demo.