1

I found an excellent explanation of expression templates here. In the article, we find a basic expression template implementation for arithmetic, as follows (slightly adapted):

#include <iostream>

template <typename T>
struct plus {
    T operator()(const T a, const T b) const { return a + b; }
};

template <class ExprT>
struct exprTraits {
    typedef ExprT expr_type;
};

template <class ExprT1, class ExprT2, class BinOp>
class BinaryExpr {
   public:
    BinaryExpr(ExprT1 e1, ExprT2 e2, BinOp op = BinOp()) : _expr1(e1), _expr2(e2), _op(op) {}
    double eval() const { return _op(_expr1.eval(), _expr2.eval()); }

   private:
    typename exprTraits<ExprT1>::expr_type _expr1;
    typename exprTraits<ExprT2>::expr_type _expr2;
    BinOp _op;
};

class Literal {
   public:
    Literal(const double v) : _val(v) {}
    double eval() const { return _val; }

   private:
    const double _val;
};

template <>
struct exprTraits<double> {
    typedef Literal expr_type;
};

class Variable {
   public:
    Variable(double& v) : _val(v) {}
    double eval() const { return _val; }

    void operator+=(double x) { _val += x; }

   private:
    double& _val;
};

class SpecialVariable : public Variable {
   public:
    SpecialVariable(double& v) : Variable{v} {};
    double eval() const { return -1000.0; }
};

template <class ExprT1, class ExprT2>
BinaryExpr<ExprT1, ExprT2, plus<double>> operator+(ExprT1 e1, ExprT2 e2) {
    return BinaryExpr<ExprT1, ExprT2, plus<double>>(e1, e2);
}

There are three type of nodes, Literal, Variable and SpecialVariable which is a subclass of the latter. The traits are there to allow for built-in types like double in the expressions without wrapping them with Literal.

Now, suppose I want to do something particular when I add a double and a Variable and plus-assign it to a Variable. I add following member function to Variable:

void operator+=(BinaryExpr<double, Variable, plus<double>> expr) { _val += 1000.0; }

and write a little test program:

int main(int argc, char const* argv[]) {
    double xd = 2.0, yd = 5.0;
    Variable x{xd};
    SpecialVariable y{yd};
    x += 3.0 + y;
    std::cout << "result : " << std::to_string(x.eval()) << "\n";
    return 0;
}

This, however, only works with Variables and not SpecialVariables, i.e. I get following compiler error:

error: no match for ‘operator+=’ (operand types are ‘Variable’ and ‘BinaryExpr<double, SpecialVariable, plus<double> >’) x += 3.0 + y;
note:   no known conversion for argument 1 from ‘BinaryExpr<double, SpecialVariable, plus<double> >’ to ‘BinaryExpr<double, Variable, plus<double> >’

which is completely reasonable since template classes do not necessarily have a relation if their template arguments have one.

Question: how can I write one operator+= that accepts expression templates with types and possibly their subtypes? I've not seen an expression template tutorial that addresses this particular problem.

Nibor
  • 1,236
  • 9
  • 23

1 Answers1

4

Question: how can I write one operator+= that accepts expression templates with types and possibly their subtypes?

Using std::is_base_of and SFINAE

template <typename V>
std::enable_if_t<std::is_base_of_v<Variable, V>>
   operator+= (BinaryExpr<double, V, plus<double>> expr)
 { _val += 1000.0; }

The preceding code compile in C++17.

If you're using C++14 you have to use

std::is_base_of<Variable, V>::value

instead of

std::is_base_of_v<Variable, V>

If you're using C++11 you have to use

typename std::enable_if<std::is_base_of<Variable, V>::value>::type

instead of

std::enable_if_t<std::is_base_of_v<Variable, V>>
max66
  • 65,235
  • 10
  • 71
  • 111
  • When I use your C++14 alternative, I get the following `error: dependent-name ‘std::is_base_of::type’ is parsed as a non-type, but instantiation yields a type` and `note: say ‘typename std::is_base_of::type’ if a type is meant` (with `g++-7.4.0` and either of the above standards) – Nibor May 09 '19 at 12:30
  • ^ never mind, I made a mistake (`::type` vs `::value` after `is_base_of`) – Nibor May 09 '19 at 12:35