How to reduce redundant code when adding new c++0x rvalue reference operator overloads

后端 未结 4 1492
不思量自难忘°
不思量自难忘° 2020-12-08 05:29

I am adding new operator overloads to take advantage of c++0x rvalue references, and I feel like I\'m producing a lot of redundant code.

I have a class, tree

相关标签:
4条回答
  • 2020-12-08 05:38

    You're supposed to define them as member functions, so that you don't have to overload on lvalue or rvalue as the primary unit (which is unnecessary anyway) That is,

    class Tree {
        Tree operator+ const (const Tree&);
        Tree operator+ const (Tree&&);
    };
    

    because the l or r valueness of the first is irrelevant. In addition, the compiler will automatically construct for you if that constructor is available. If tree constructs from double, then you can automatically use doubles here, and the double will be appropriately an rvalue. This is just two methods.

    0 讨论(0)
  • 2020-12-08 05:40

    I think the problem is that you have defined the operation with non const parameters. If you define

    tree operator +(const tree& a, const tree& b);
    

    There is no difference between r-value and l-value reference, so you don't need to define also

    tree operator +(tree&&      a, const tree& b);
    

    If in addition double is convertible to tree as tree x = 1.23; lets think, you don't need neither define

    tree operator +(double      a, const tree& b){ return tree(a) + b; }
    

    the compiler will do the work for you.

    You will need to make the difference between rvalues and lvalues if the operator+ takes the tree parameter by value

    tree operator +(tree a, tree b);
    
    0 讨论(0)
  • 2020-12-08 05:45

    First, I don't see why operator+ would modify the arguments at all (isn't this a typical immutable binary tree implementation), so there'd be no difference between r-value and l-value reference. But let's assume that the subtrees have a pointer up to the parent or something like that.

    From the usage example you showed, it looks like there's an implicit conversion from double to tree. In that case, your "cast and forward" cases aren't needed, the compiler will find the user-defined conversion.

    Don't the non-move overloads end up making a new instance to go into the new tree? If so, I think you can write three of your remaining four cases as forwarders.

    tree operator +(tree&& a, tree&& b); // core case
    tree operator +(tree   a, tree   b) { return std::move(a) + std::move(b); }
    tree operator +(tree   a, tree&& b) { return std::move(a) + std::move(b); }
    tree operator +(tree&& a, tree   b) { return std::move(a) + std::move(b); }
    

    Of course, you can use a macro to help generate the three (or seven) forwarding versions of each operator.

    EDIT: if those calls are ambiguous or resolve to recursion, how about:

    tree add_core(tree&& a, tree&& b);
    tree operator +(tree&& a, tree&& b) { return add_core(std::move(a), std::move(b)); }
    tree operator +(tree   a, tree   b) { return add_core(std::move(a), std::move(b)); }
    tree operator +(tree   a, tree&& b) { return add_core(std::move(a), std::move(b)); }
    tree operator +(tree&& a, tree   b) { return add_core(std::move(a), std::move(b)); }
    

    EDIT: repro of the operator failure to use implicit conversions:

    #include <iostream>
    
    template<typename T>
    class tree;
    
    template<typename T> tree<T> add(tree<T> a, tree<T> b)
    {
        std::cout << "added!" << std::endl << std::endl;
        return tree<T>();
    }
    
    template<typename T> tree<T> operator +(tree<T>   a, tree<T>   b) { return add(a, b); }
    
    template<typename T>
    class tree
    {
    public:
        tree() { }
        tree(const tree& t) { std::cout << "copy!" << std::endl; }
        tree(double val)    { std::cout << "double" << std::endl; }
        friend tree operator +<T>(tree a, tree b);
    };
    
    int main()
    {
        tree<double>(1.0) + 2.0;
        return 0;
    }
    

    And version without templates where the implicit conversion works:

    #include <iostream>
    
    class tree
    {
    public:
        tree() { }
        tree(const tree& t) { std::cout << "copy!" << std::endl; }
        tree(double val)    { std::cout << "double" << std::endl; }
    friend tree operator +(tree a, tree b);
    };
    
    tree add(tree a, tree b)
    {
        std::cout << "added!" << std::endl << std::endl;
        return tree();
    }
    
    tree operator +(tree a, tree b) { return add(a, b); }
    
    int main()
    {
        tree(1.0) + 2.0;
        return 0;
    }
    
    0 讨论(0)
  • 2020-12-08 05:57

    Just a quick late answer: If the class in question is moveable, the move is very cheap, and you would always move from all the arguments if you can, then passing the arguments by value might be an option:

    tree operator +(tree      a, tree      b);
    

    If tree is moveable and an rvalue ref is passed as the actual argument, then the arguments to the function will be initialized with tree's move constructor where possible, else the copy constructor. Then, the function can do whatever it wants with its arguments in the appropriate way (like, say, moving their internals around).

    It does incur an extra move when passing an rvalue reference argument compared with the lots-of-overloads version, but I think it's generally better.

    Also, IMO, tree && arguments should maybe accept lvalues via a temporary copy, but this is not what any compilers currently do, so it's not very useful.

    0 讨论(0)
提交回复
热议问题