Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

To forward or move or not; how to determine which is preferred within the context of a class's usage?

class Test {
public:
    typedef std::set<std::pair<double, double>> DataSet;

    explicit Test(const DataSet&& d) {
        for (auto &itr : d) {
            std::cout << "value1 = " << itr.first << '\n';
            std::cout << "value2 = " << itr.second << '\n';
        }
    }

};

int main() {
    //using namespace util;
    try {
        // Forwarding
        Test obj( std::forward<Test::DataSet>(
            { 
              { 10.0, 20.0 },
              { 30.0, 40.0 },
              { 50.0, 60.0 }
            }
        ));

        std::cout << '\n';

        // Move
        Test obj2(std::move(Test::DataSet(
            {
              { 10.0, 20.0 },
              { 30.0, 40.0 },
              { 50.0, 60.0 }
            }
           )
        ));

    } catch (const std::exception& e) {
        std::cerr << e.what() << std::endl;
        return EXIT_FAILURE;
    }

    return EXIT_SUCCESS;
}
like image 870
Francis Cugler Avatar asked Jan 18 '26 08:01

Francis Cugler


2 Answers

Neither.

Test::DataSet(/*...*/) is already a rvalue that can bind to a rvalue reference. The only thing std::move does is to turn a lvalue into a xvalue (which is a rvalue), so that it can bind to a rvalue reference, which lvalues cannot. So std::move is redundant.

std::forward does the same only conditional on its template argument. So it is redundant (at best) as well.

const DataSet&& as parameter makes no sense. You want either DataSet&& which binds only to rvalues or const DataSet&, which binds to rvalues and lvalues.

If you want to make use of the fact that the passed reference might be a rvalue (i.e. by moving from its members), then you need two overloads of your function. One taking DataSet&& which is called with rvalue arguments and one taking const DataSet& which is called with lvalue arguments.

In the specific case of the Test function in your question, I don't see how there would be any benefit to moving from the members or do any other modification of the DataSet state, so I would go with simply const DataSet& as parameter and never bother with std::moveing or std::forwarding into that function specifically.

like image 95
walnut Avatar answered Jan 20 '26 02:01

walnut


[Code samples edited in response to comments]
A useful bit of advice from the ISO Core Guidelines. This would suggest that you should prefer (const T&), but if you really need speed add a (T&&) specifically. This way you can pass small sets as (L-Value)

const auto data = Test::DataSet{ 
          { 10.0, 20.0 },
          { 30.0, 40.0 },
          { 50.0, 60.0 }};
Test obj( data );

or (R-Value)

Test obj( Test::DataSet{ 
          { 10.0, 20.0 },
          { 30.0, 40.0 },
          { 50.0, 60.0 },
        });

Adding the extra move makes the second kind of call more efficient, but the advice is to only use this type of optimization if it is required. You should avoid having different semantics for one constructor than for the other, and the easiest way to avoid that is to only have one constructor.

Personally, even after 20+ years of C++ programming, I found the table in the link so useful I printed it out and stuck it to my monitor.

like image 29
Tiger4Hire Avatar answered Jan 20 '26 01:01

Tiger4Hire



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!