2

Please consider the following code snippet:

template<class E>
class vector_expression
{};

template<class Tuple>
class vector
    : public vector_expression<vector<Tuple>>
{};

template<class E1, class E2, class BinaryOperation>
class vector_binary_operation
    : public vector_expression<vector_binary_operation<E1, E2, BinaryOperation>>
{
public:
    vector_binary_operation(E1&& e1, E2&& e2, BinaryOperation op)
        : m_e1(e1), m_e2(e2),
          m_op(op)
    {}

private:
    E1 m_e1;
    E2 m_e2;
    BinaryOperation m_op;
};

template<class E1, class E2>
vector_binary_operation<E1, E2, std::plus<>> operator+(E1&& e1, E2&& e2) {
    return{ std::forward<E1>(e1), std::forward<E2>(e2), std::plus<>{} };
}

The code above ensures that vector_binary_operation stores references to named objects and makes copies for temporaries. The problem is the interface of operator+, cause it actually defines this operator for any type. What do I need to change, if I want to preserve the functionality, but only define the operator for types being or derived from vector_expression?

0xbadf00d
  • 17,405
  • 15
  • 67
  • 107
  • 1
    The initializer list in your constructor should better be: `m_e1(std::forward(e1)), m_e2(std::forward(e2)), m_op(std::move(op))`. Otherwise you break the perfect-forwarding chain. – davidhigh Apr 08 '16 at 22:31
  • @davidhigh Yes, you're right. I've copied the code from the other question and there the parameters `e1` and `e2` were const references. Thanks for noting that. – 0xbadf00d Apr 09 '16 at 10:12
  • no, I'm wrong. I would be right if the constructor were a function template, something like `template vector_binary_operation( E1_&& e1, E2_&& e2, BinaryOperation op)`. Btw, this is probably what you intended when you used the `&&`. For more info, see [here](https://isocpp.org/blog/2012/11/universal-references-in-c11-scott-meyers) – davidhigh Apr 09 '16 at 10:31
  • @davidhigh With `template using dynamic_vector = vector>;` and `dynamic_vector x, y;`, the type of `e1` and `m_e1` in `x + y` will be `dynamic_vector&` and `dynamic_vector&`, respectively. The type of `e1` and `m_e1` in `std::move(x) + y` will be `dynamic_vector&&` and `dynamic_vector`, respectively. So, could you please explain in more detail what you mean? – 0xbadf00d Apr 09 '16 at 11:37
  • in order to have "universal references" the template parameter type needs to be deduced. In your case, you could get problems when `E1` is a `dynamic_vector`, say. The constructor then expects an rvalue-reference to `dynamic_vector`, and does not allow to pass an lvalue-reference to it (which would lead to a copy to set up the member `m_e1`). There's no need to constrain the class like this. – davidhigh Apr 09 '16 at 11:52
  • ... so: (i) either use the constructor template two comments before, (ii) or provide overloads for both rvalue- and lvalue-refs (that grows exponentially in the number of parameters, however), (iii) or use const-references throughout and rely on your compiler's optimization features (like copy-elision). – davidhigh Apr 09 '16 at 11:54
  • @davidhigh (1) I cannot use a constructor template, cause I need the types `E1` and `E2` in order to declare the member variables. (2) I still don't get your argument. Why should I get problems when `E1 = dynamic_vector`? Yes, the constructor then expects a `dynamic_vector&&`. But since instances of `vector_binary_operation` are created by the overload operator(s) (like `operator+`) only and the operator implementation(s) ensure that a `dynamic_vector&&` will be passed to the constructor whenever `E1 = dynamic_vector`, I don't see any problem. – 0xbadf00d Apr 09 '16 at 12:32
  • agreed, when you access it only through a specified set of functions and those meet the requirements, fine. My point was more general (--why restrict the capabilities). And just to clarify: alternative (i) above works well, the idea is to retain the class template arguments and use another different set of template parameters for the constructor ... see [here](http://coliru.stacked-crooked.com/a/0a239d6f1cdec258). – davidhigh Apr 09 '16 at 13:25

2 Answers2

2

You could use SFINAE and std::is_base_of in the following manner:

template<class E1, class E2>
std::enable_if_t<std::is_base_of_v<vector_expression<std::decay_t<E1>>, std::decay_t<E1>>&&
                 std::is_base_of_v<vector_expression<std::decay_t<E2>>, std::decay_t<E2>>,
                 vector_binary_operation<std::decay_t<E1>, std::decay_t<E2>, std::plus<>>>
operator+(E1&& e1, E2&& e2) {
    return {
             std::forward<std::decay_t<E1>>(e1),
             std::forward<std::decay_t<E2>>(e2),
             std::plus<>{}
           };
}

Live Demo

101010
  • 41,839
  • 11
  • 94
  • 168
1

@101010 already gave the principle. I'm extending it a bit as you asked this question also in a comment of one of my answers.

As I guess you are overloading more operators than operator+, it's convenient to write a traits class which checks whether all passed types are vector_expressions:

template<typename ... Ts> struct is_vector_expression 
     : public std::false_type {};
template<typename T> struct is_vector_expression<T>
     : public std::is_base_of<vector_expression<std::decay_t<T> >, std::decay_t<T> >::type {};
template<typename T, typename ... Ts> struct is_vector_expression<T, Ts ...>
     : public std::integral_constant<bool, is_vector_expression<T>::value
                                        && is_vector_expression<Ts ...>::value> {};

When using C++17, you can also skip the variadic template stuff and use std::conjunction.

Next you can wrap that inside a suitable alias, so that you don't have to write std::enable_if_t all the time:

template<typename ... Ts>
using enable_for_vector_expression = std::enable_if_t<is_vector_expression<Ts...>::value>;

Finally, you can use that as in the following example for all your overloaded operators:

template<typename E1, typename E2,
         typename = enable_for_vector_expression<E1, E2> >
auto operator+(E1&& e1, E2&& e2)
{
    //...
}
Community
  • 1
  • 1
davidhigh
  • 14,652
  • 2
  • 44
  • 75
  • How would you implement your `is_vector_expression` trait with `std::conjunction`? – 0xbadf00d Apr 09 '16 at 12:50
  • Maybe `template using enable_if_vector_expression_t = std::enable_if_t>, std::decay_t>...>>;`? – 0xbadf00d Apr 09 '16 at 13:24
  • @0xbadf00d: I would apply it on the level of `is_vector_expression` (and leave the alias as it is), but otherwise -- just in the way you wrote. – davidhigh Apr 09 '16 at 13:27
  • I think now that I will just use `template constexpr bool is_vector_expression_v = std::is_base_of_v>, std::decay_t>;` and `class = std::enable_if_t && is_vector_expression_v>` as a third template argument for each operator. – 0xbadf00d Apr 09 '16 at 17:14