This is the basic implementation I'm using for expression templates, based on the CRTP which allows me to conveniently combine multiple types of operations without also asking everything to be an expression tree.
template<typename E>
struct OpExpression
{
auto eval(iter_type n) const
{
return static_cast<E const&>(*this).eval(n);
}
};
template<typename T>
struct OpFrame : OpExpression<OpFrame<T>>
{
T *ptr;
OpFrame(T *ptr) : ptr{ ptr } {}
T eval(iter_type n) const
{
return ptr[n];
}
};
template<typename T>
struct OpLiteral : OpExpression<OpLiteral<T>>
{
T value;
OpLiteral<T>(T value) : value{ value } {}
T eval(iter_type) const
{
return value;
}
operator T() const
{
return value;
}
};
Here is a class to apply the addition between two values (and an operator overload to make it pretty):
template<typename E1, typename E2>
struct OpFrameAdd : OpExpression<OpFrameAdd<E1, E2>>
{
OpFrameAdd(E1 const a, E2 const b) : a{ a }, b{ b } {}
auto eval(iter_type n) const
{
return a.eval(n) + b.eval(n);
}
protected:
E1 const a;
E2 const b;
};
template<typename E1, typename E2>
auto operator+(OpExpression<E1> const& a, OpExpression<E2> const& b)
{
auto v = OpFrameAdd<E1, E2>(*static_cast<const E1*>(&a), *static_cast<const E2*>(&b));
return v;
}
To provide some more detail/context; I have an array holding a bunch of values (could be of various types), and an arithmetic expression defining how I want to transform that array into another one. However, this transformation is not concrete, so I'm using the expression tree as something I can pass to objects that will then handle it in different ways (i.e. I have to wait to evaluate it). Additionally, I only want to define the expression once.
I'm wondering, if based on this design, I can introduce literals (without casting to an OpLiteral
) to my expressions? For example:
double arr[100]{ 0 };
OpFrame arr_t(arr);
// I can do this
auto ev1 = arr_t + arr_t + arr_t + OpLiteral(2.0);
// But I would prefer to do this
auto ev2 = arr_t + arr_t + arr_t + 2.0;
Based on my question here, I know 2.0
won't automatically cast to the correct type, but the solution is also not compatible with this design (it causes either an ambiguous call, or in bigger expressions, mixes up the tree by applying the generic template rather than one based on OpExpression<T>
).
How I had tried to implement that solution:
template<typename E1, typename T>
auto operator+(OpExpression<E1> const& a, T b)
{
auto v = OpFrameAdd<E1, OpLiteral<T>>(*static_cast<const E1*>(&a), OpLiteral<T>(b));
return v;
}
template<typename E2, typename T>
auto operator+(T a, OpExpression<E2> const& b)
{
auto v = OpFrameAdd<OpLiteral<T>, E2>(OpLiteral<T>(a), *static_cast<const E2*>(&b));
return v;
}
So my questions are:
Is it possible to augment the design to use the literal in the preferred way? If not, is this is just a limitation of the templates/design I chose? (Casting to OpLiteral
is still a lot easier than making a new operator overload for each type and for both sides). More broadly, is there a known (different) design to deal with this problem? Have I applied this design correctly to the problem?
EDIT
With the given design, it doesn't appear possible to accept and convert another type implicitly. Ultimately, it seems the problem can be phrased as: I want to have an operation between 1) A parent class and another parent class; 2) A parent class an any other object; 3) Any class and that parent class. Obviously this is inherently problematic.
For instance, when I attempted to remedy the ambiguous call in the above attempt, it instead results in operations between children of OpExpression
becoming a template argument of OpLiteral
. This is because instead of resolving the 'correct' operator (i.e. applying operator with both argument types of OpExpression
), it will choose the one with the more generic argument type.
The conclusion is that as it is, this is simply a limitation of the design (and for good reason).
However, suppose I have a complete list of all the literal types I would want to use in an expression. If I don't want to create individual template specializations for each one, how would I modify the overloaded operator to use that? Would I instead have to modify the classes of OpExpression
?