按值传递引用参数

12

考虑这个简单的程序:

vector<int> foo = {0, 42, 0, 42, 0, 42};
replace(begin(foo), end(foo), foo.front(), 13);

for(const auto& i : foo) cout << i << '\t';

当我写这段代码的时候,我预期的输出结果是:

13 42 13 42 13 42

但实际上输出结果是:

13 42 0 42 0 42

问题在于, replace 函数传入的最后两个参数是引用。因此如果它们中的任意一个处于被操作的范围内,结果可能会出乎意料。我可以通过添加一个临时变量来解决这个问题:
vector<int> foo = {0, 42, 0, 42, 0, 42};
const auto temp = foo.front();
replace(begin(foo), end(foo), temp, 13);

for(const auto& i : foo) cout << i << '\t';

我知道C++11给我们提供了各种类型工具,但我想知道是否可能将该值强制转换为非引用类型并在内联时传递,而不创建临时对象?


8
虽然不是解决方案/答案,但你可以编写 replace(begin(foo), end(foo), int(foo.front()), 13); 这段代码,可以使用对象的复制构造函数进行模板化。我觉得你需要使用某种类型的副本。 - thorsan
@thorsan,实际上,这是一个(有点狡猾的)解决方案,所以你也应该发布它。 - awesoon
2
@thorsan,这不是我想要的答案,但当我仔细看它时,我越来越喜欢它。我认为你可以提出一个可靠的方案,这将是最好和最简单的方法。如果你把它写成答案,我至少会点赞给你。 - Jonathan Mee
你能以某种方式移动输入吗?毕竟你已经在覆盖它了。 - thorsan
1
@thorsan 不,我在这种情况下简化了它,以便将其转换为最小完整可验证示例。 我正在研究的情况可能来自范围内的任何地方,我不知道具体位置。 - Jonathan Mee
对,你甚至连第一个都不能移动,它找不到。 - thorsan
7个回答

9
一个解决方案可能如下(即使它只是临时的)
template<class T>
void replace_value_of_first(std::vector<T>& v, const T& value)
{
    std::replace(v.begin(), v.end(), T(v.front()), value);
}

正在这里讨论,但是static_cast在这里提供了一些额外的保护。虽然没有它也是一个不错的解决方案。 - Jonathan Mee
嗯...我已经更新了相关问题的链接来表达问题,但在Visual Studio 2015上无法工作。 - Jonathan Mee
对于Visual Studio用户,您可以获得C++11兼容性,因此支持此功能而无需修改单个项目:https://dev59.com/9Jjga4cB1Zd3GeqPH0as。但是,即使这是标准兼容的,在没有链接步骤的情况下,Visual Studio 2015也不会正确地运行。 - Jonathan Mee

7

您可以编写一个简单的函数,它接受一个引用并返回一个值。这将“转换”引用为值。这确实会生成一个临时对象,但它是未命名的,并将在完整表达式结束时被销毁。例如:

template<typename T>
T value(const T& ref)
{
    return ref;
}

然后你可以像这样使用它

int main()                                                   
{                                                            
    vector<int> foo = {0, 42, 0, 42, 0, 42};
    replace(begin(foo), end(foo), value(foo.front()), 13);

    for(const auto& i : foo) cout << i << '\t';                                      
}

输出:

13  42  13  42  13  42

Live Example


4
你可以将给定的值转换为rvalue以实现所需效果。下面的示例可行,无需定义任何额外的函数,只需将零添加到该值即可。
vector<int> foo = {0, 42, 0, 42, 0, 42};
replace(begin(foo), end(foo), foo.front()+0, 13);

for(const auto& i : foo) cout << i << '\t';

甚至可以(正如Jarod42所建议的那样)仅使用一元操作符+,它是一个无操作符:
vector<int> foo = {0, 42, 0, 42, 0, 42};
replace(begin(foo), end(foo), +foo.front(), 13);

for(const auto& i : foo) cout << i << '\t';

显然,这两种方法仍然会创建临时文件。我认为你无法避免这个问题。

3
甚至可以用+foo.front() - Jarod42
3
顺便提一句,你的解决方案并不适用于任何“T”(例如“std::string”)的一般情况。 - Jarod42
1
@JonathanMee:它是一个无操作,不改变值(即使为负)。它相当于他的 +0 - Jarod42
1
@JonathanMee 这实际上相当于 +0,因为 +int() 的意思就是零。 - W.F.
@max66 是的,通常使用T()更有意义,因为它具有通用兼容性,然而Visual Studio 2015默认不支持。所以在这种情况下,一元加运算符可能是最好的选择。 - Jonathan Mee
显示剩余8条评论

1
在这种特殊情况下(当旧值是向量的第一个元素时),您可以使用rbegin()rend()反转替换顺序。
一般来说,我不知道是否有简单的方法,在不进行复制的情况下实现。
int main ()
 {
   std::vector<int> foo = {0, 42, 0, 42, 0, 42};
   std::replace(foo.rbegin(), foo.rend(), foo.front(), 13);

   for(const auto & i : foo)
      std::cout << i << '\t';

   std::cout << std::endl;

   return 0;
 }

p.s.: 对不起,我的英语不好。


1
为了更加明确,您可以使用int()作为构造函数创建一个临时变量:
replace(begin(foo), end(foo), int(foo.front()), 13);

不是添加一个值。请参见演示

你来晚了一点:https://dev59.com/iFoT5IYBdhLWcg3w-DO6#37967761 但是那个和这个都是最好的解决方案。 - Jonathan Mee

1

适用于任何类型而不仅仅是数字的一行代码:

replace(begin(foo), end(foo), make_pair(foo.front(),0).first, 13);

或者不创建额外的字段:
replace(begin(foo), end(foo), get<0>( make_tuple(foo.front()) ), 13);

1
vector<int> foo = {0, 42, 0, 42, 0, 42};
replace(begin(foo), end(foo), static_cast<int>(foo.front()), 13);
assert(equal(begin(foo), end(foo), begin({13, 42, 13, 42, 13, 42})));

哈哈:https://dev59.com/45jga4cB1Zd3GeqPHkPb?lq=1 结果在 Visual Studio 2015 上无法正常工作。 - Jonathan Mee

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接