这个问题是从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,也不能专门处理右值引用。
右值引用只允许使用一次。
您可以做的是为最后一个电话打一个
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
本身已收到右值引用,则它会为最后一个元素调用右值引用的重载。在所有其他情况下,将调用左值引用的重载。