12

Consider this simple program:

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';

When I wrote it I expected to get:

13 42 13 42 13 42

But instead I got:

13 42 0 42 0 42

The problem of course is that replace takes in the last 2 parameters by reference. So if either of them happen to be in the range being operated on the results may be unexpected. I can solve this by adding a temporary variable:

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';

I do know that C++11 gave us all kinds of type tools is it possible that I could simply force this value to a non-reference type and pass that inline, without creating the temporary?

Jonathan Mee
  • 37,899
  • 23
  • 129
  • 288
  • 8
    Not a solution/answer, but you could write `replace(begin(foo), end(foo), int(foo.front()), 13);`which could be templatetized using the copy constructor of the object. I feel you have to use a copy of some sort. – thorsan Jun 22 '16 at 12:08
  • @thorsan, Actually, this is a (little bit hacky) solution, so you should post it as well. – awesoon Jun 22 '16 at 12:12
  • 2
    @thorsan Not the answer I was hoping for, but the more that I look at it the more I like it. I think that you could present a defensible case that is going to be the best and the simplest way to do it. If you were to write that up as an answer you'd at least get an upvote from me. – Jonathan Mee Jun 22 '16 at 12:13
  • Could you in some way move the input since you are overwriting it anyway? – thorsan Jun 22 '16 at 12:13
  • 1
    @thorsan No, I've simplified it in this case to get it into a [minimal, complete, verifiable example](http://stackoverflow.com/help/mcve) The case I'm looking at *may* come from anywhere in the range and I won't know where. – Jonathan Mee Jun 22 '16 at 12:16
  • True, you couldnt even move the first, it wouldnt find it. – thorsan Jun 22 '16 at 12:19

7 Answers7

9

A solution could be as follows (even though you are making a temporary)

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);
}
W.F.
  • 13,888
  • 2
  • 34
  • 81
thorsan
  • 1,034
  • 8
  • 19
  • It's being discussed [over here](http://stackoverflow.com/q/37969114/2642059) but a `static_cast` provides some additional protection here. Though this is a fine solution without it. – Jonathan Mee Jun 22 '16 at 13:34
  • Hmm... I've updated [the linked question](http://stackoverflow.com/q/37969114/2642059) to express the problem, but this doesn't work on Visual Studio 2015. – Jonathan Mee Jun 22 '16 at 13:55
  • For Visual Studio users you can get C++11 compliance so this is supported without modifying individual projects: http://stackoverflow.com/q/37976395/2642059 **But even though this is standard compliant, without the linked step, Visual Studio 2015 will not behave correctly here.** – Jonathan Mee Jun 23 '16 at 11:48
7

You can write a simple function that takes in a reference and returns a value. this will "convert" the reference into a value. This does generate a temporary but it is unnamed and will be destroyed at the end of the full expression. Something like

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

And then you can use it like

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';                                      
}

output:

13  42  13  42  13  42

Live Example

NathanOliver
  • 171,901
  • 28
  • 288
  • 402
4

You can convert the given value into an rvalue to achieve the desired effect. The example below works, without defining any extra functions, simply by adding zero to the value.

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';

Or even (as suggested by Jarod42) just the unary + operator, which is a no-op:

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';

Obviously either of these still create a temporary. I don't think you can get away from that.

Smeeheey
  • 9,906
  • 23
  • 39
  • Yes, that's even better. Updated answer – Smeeheey Jun 22 '16 at 12:21
  • 3
    Or even `+foo.front()`. – Jarod42 Jun 22 '16 at 12:25
  • 3
    Btw, your solution doesn't work in general case for any `T` (as for `std::string`). – Jarod42 Jun 22 '16 at 12:26
  • Agreed, though I don't think that was a requirement – Smeeheey Jun 22 '16 at 12:27
  • @Jarod42 What would that do? Is that the unary sign operator? Would that have any effect on the number if it was negative? – Jonathan Mee Jun 22 '16 at 12:41
  • 1
    @JonathanMee: it is a no-op, and don't change the value (even if negative). it is equivalent to his `+0`. – Jarod42 Jun 22 '16 at 12:57
  • @Jarod42 So it makes a great solution for numeric values (which I am working with) but as you say it won't work for non-numeric types. – Jonathan Mee Jun 22 '16 at 13:00
  • @JonathanMee for string couldn't it be `foo.front()+std::string()`? – W.F. Jun 22 '16 at 13:13
  • 1
    @JonathanMee this would be actually equivalent as `+0` means actually `+int()` – W.F. Jun 22 '16 at 13:17
  • @JonathanMee - in other words, `T()`, `int()`, `+`, `+0`, `+int()`, etc., are all elegant solutions but all of they create a temporary variable. – max66 Jun 23 '16 at 00:30
  • @max66 Yes, and generally it would make more sense to use `T()` for generic compatibility, however [Visual Studio 2015 doesn't support that by default](http://stackoverflow.com/q/37969114/2642059). So in this case the unary plus operator is probably the best bet. – Jonathan Mee Jun 23 '16 at 01:22
  • @JonathanMee - wow: I didn't know the Visual Studio problem; and yes, I'm agree with you: `T()` it's the better solution because it's (IMHO) clearer when someone else see the code. – max66 Jun 23 '16 at 21:07
  • @max66 I've up-voted this and almost accepted it, but I found that you can essentially correct Visual Studio's incompatibility with 2 simple edits: http://stackoverflow.com/q/37976395/2642059 I feel that `static_cast` is a little better form, that just `T{}` but it achieves the same thing, and I agree should be preferred to the unary plus operator for clarity. Really interesting topic though, I learned a lot on this question. – Jonathan Mee Jun 23 '16 at 23:25
1

In this particular case (when the old value is the first of the vector), you can reverse the order of substitution with rbegin() and rend().

In general, I don't know if it's possible, in a simple way, without make a copy.

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.: sorry for my bad English.

max66
  • 65,235
  • 10
  • 71
  • 111
1

To be more explicit you can use int() as a constructor to create a temporary:

replace(begin(foo), end(foo), int(foo.front()), 13);

Instead of adding a value. See Demo.

kwarnke
  • 1,424
  • 1
  • 15
  • 10
  • You're a little late to the party: http://stackoverflow.com/a/37967761/2642059 But that and [this](http://stackoverflow.com/questions/37967446/passing-to-a-reference-argument-by-value/37968684#comment63381609_37967837) are the best solutions. – Jonathan Mee Jun 22 '16 at 12:58
1

One liner that should work for any type, not only numeric:

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

or without creating extra field:

replace(begin(foo), end(foo), get<0>( make_tuple(foo.front()) ), 13);
Slava
  • 43,454
  • 1
  • 47
  • 90
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})));
  • Haha: http://stackoverflow.com/questions/37969114/what-does-static-castt-do-to-a-t?lq=1 turns out it doesn't work on Visual Studio 2015. – Jonathan Mee Jun 22 '16 at 15:10