The practical way to do this kind of thing is with expression templates.
I'd change your return values from operator[]
to an expression template even.
This will use C++11 features, because it makes it shorter.
enum class ExpressionType { Index, Addition };
template< ExpressionType Op, typename LHS, typename RHS >
struct Expression {
LHS lhs;
RHS rhs;
template<typename T>
operator T();
};
// to separate out the evaluation code:
template< typename T, ExpressionType Op, typename LHS, typename RHS >
struct Evaluate {
T operator()( Expression<Op, LHS, RHS> exp ) const;
};
template< ExpressionType Op, typename LHS, typename RHS >
template<typename T>
Expression<Op,LHS,RHS>::operator T() {
return Evaluate<T,Op,LHS,RHS>()( std::move(*this) );
}
// further specializations needed:
template< typename T, typename RHS >
struct Evaluate< T, ExpressionType::Index, data, RHS > {
T operator()( Expression<Op, ExpressionType::Index, data, RHS> exp ) const {
// we just assume RHS can be treated like an integer. If it cannot,
// we fail to compile. We can improve this with SFINAE elsewhere...
return exp.lhs.get_nth(exp.rhs);
}
};
template< typename T, typename LHS, typename RHS >
struct Evaluate< T, ExpressionType::Addition, LHS, RHS > {
T operator()( Expression<Op, ExpressionType::Index, data, RHS> exp ) const {
// code with all of LHS, RHS and T visible!
}
};
template<typename E>
struct is_expression : std::false_type {};
template<ExpressionType Op, typename LHS, typename RHS>
struct is_expression<Expression<Op,LHS,RHS> : std::true_type {};
template<ExpressionType Op, typename LHS, typename RHS>
Expression<Op, LHS, RHS> make_expression( LHS&& lhs, RHS&& rhs ) {
return { std::forward<LHS>(lhs), std::forward<RHS>(rhs) };
}
// here is why I want to start out with returning an expression. This SFINAE test
// is extremely easy because of that -- we overload operator+ on any two types, so long
// as one of them is an Expression!
template<typename LHS, typename RHS, typename=typename std::enable_if<is_expression<LHS>::value || is_expression<RHS>::value >::type>
ExpressionType<ExpressionType::Addition, LHS, RHS> operator+( LHS&& lhs, RHS&& rhs )
{
return make_expression<ExpressionType::Addition>(std::forward<LHS>(lhs), std::forward<RHS>(rhs) );
}
so the idea is, we build at compile time a tree of templates that represent the order in which various expressions are evaluated by the compiler.
When we finally cast it to a concrete type T
, only then do we start the evaluation work.
This avoids having to create any temporaries, but does mean we have to do a lot of template mojo in order to get things up and running. The above is a sketch of such an template expression tree generator.
To see a complete implementation of a simple case, here is a link to wikipedia's article on the subject, where a full blown expression tree system is built up to do std::vector
vector processing without temporaries.