c++如何在函数中多次使用通用引用

问题描述 投票:0回答:1

这个问题是从Code Review迁移过来的,因为它在那边被标记为离题

我需要将右值引用(临时)作为参数传递给采用通用引用的函数。它可以在里面多次使用。每个调用它的函数都接受右值引用作为特殊重载。我的问题是我们应该如何转发参数。

以下是一个例子:

#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"));
}

试运行

如您所见,当我们只需要使用一次时,如

play_by_first
,完美转发(
std::forward
)将是最终的解决方案。但是,当它多次使用时,第一次调用后右值将失效。

现代 c++ 中是否有标准方法来处理这个问题?我仍然想对临时对象进行一些优化。我希望有一些标准的解决方案来利用它。

我还查看了 std 库,尝试从

find_if
等实现中学习,其中谓词可以是右值引用并将被多次调用。但是,它不采用 universal reference,也不能专门处理右值引用。

c++11 perfect-forwarding
1个回答
0
投票

右值引用只允许使用一次。

您可以做的是为最后一个电话打一个

move
,为所有以前的电话打一个
copy
。不幸的是,这使代码有点复杂。

#include <span>

template <typename G>
void play(G&& game) {
    if(students.empty()) {
        return;
    }

    for (auto& student : std::span(students.begin(), students.end() - 1)) {
        student.play(game);
    }

    students.back().play(std::forward<G>(game));
}

目前没有普遍适用的功能。但是你当然可以写一个:

#include <vector>
#include <algorithm>

template <typename F, typename E, typename T>
constexpr void invoke_for_any(F f, std::vector<E>& vec, T&& v) {
    if(vec.empty()) {
        return;
    }

    std::for_each(vec.begin(), vec.end() - 1, [&](E& e){ f(e, v); });

    f(vec.back(), std::forward<T>(v));
}

// ...

template <typename G>
void play(G&& game) {
    invoke_for_any(
        []<typename T>(Student& student, T&& game){
            student.play(std::forward<T>(game));
        }, students, std::forward<G>(game));
}

这里重要的是,lambda 函数是一个模板,可以接收第二个参数作为左值或右值引用。如果函数

invoke_for_any
本身已收到右值引用,则它会为最后一个元素调用右值引用的重载。在所有其他情况下,将调用左值引用的重载。

© www.soinside.com 2019 - 2024. All rights reserved.