samedi 18 mars 2023

c++ how to use universal reference in a function multiple times

This question is migrated from Code Review since it is marked off-topic over there

I need to pass an rvalue-reference (temporary) as a parameter to a function taking unversal reference. It be used multiple times inside. Each function calling it accepts rvalue-reference as special overload. My question is how we should forward the parameter.

Following is an example:

#include <iostream>
#include <vector>
using namespace std;

struct Game {
    Game(const std::string& n) : name(n) {}
    Game(std::string&& n) : name(std::move(n)) {}
    std::string name;
};

struct Student {
    // constructor: not important for the discussion
    Student(const string& n) : name(n){}
    Student(string&& n) : name(std::move(n)) {}

    // play has overloaded for several different types
    void play(const std::string& game) {
        cout << name << " plays |" << game << "| (L)"<< endl;
        games.emplace_back(game);
    }
    void play(std::string&& game) {
        cout << name << " plays |" << game << "| (R)"<< endl;
        games.emplace_back(std::move(game));
    }
    void play(const Game& game) {
        cout << name << " plays |" << game.name << "| (L)"<< endl;
        games.emplace_back(game);
    }
    void play(Game&& game) {
        cout << name << " plays |" << game.name << "| (R)"<< endl;
        games.emplace_back(std::move(game));
    }
    std::string name;
    std::vector<Game> games;
};

struct Class {
    // construct: not important here. Did not overload for &&
    Class(const vector<string>& names) {
        for (auto name : names) {
            students.emplace_back(std::move(name));
        }
    }
    
    // perfect forwarding works nice
    template <typename G>
    void play_by_first(G&& game) {
        if (students.size() == 0) return;
        students[0].play(std::forward<G>(game));
    }

    // this is relevant part. 
    // How to code for a generic game of type G and 
    // game might or might not be temporary
    template <typename G>
    void play(G&& game) {
        for (auto& student : students) {
            student.play(std::forward<G>(game)); // <--- how to change this line
        }
    }
    vector<Student> students;
};

int main() {
    // normal here
    Class grade1({"alice"});
    grade1.play("football");
    grade1.play(Game("basketball"));

    cout << endl;
    // Charlie has nothing to play
    Class grade2({"bob", "Charlie"});
    grade2.play(std::string{"football"});
    grade2.play(Game("basketball"));
}

try run

As you can see, when we only need to use it once, as in play_by_first, perfect forwarding (std::forward) will be the ultimate solution. However, when it used multiple times, the rvalues will be invalid after the first call.

Is there a standard way in modern c++ to handle this? I still wanna to have some optimization for temporaries. So at least the last call should resolve to the rvalue reference overload.

I also looked into the std library, tried to learn from implementations such as find_if where the predicator can be an rvalue reference and will be called multiple times. Howerver, it does not take universal reference, and cannot handle rvalue references specially.

Aucun commentaire:

Enregistrer un commentaire