6

In the standard (20.2.2 [utility.swap]), std::swap is defined for lvalue references. I understand that this is the common case for when you want to swap two things. However, there are times when it's correct and desirable to swap rvalues (when temporary objects contain references, like here: swap temporary tuples of references).

Why isn't there an overload for rvalues? Does the risk of doing a meaningless swap on rvalues outweigh the potential benefits?

Is there a legal way to support swapping rvalue std::tuple objects which contain references? For a user defined type, I would specialize swap to accept its arguments by value, but it doesn't seem kosher to do the same for a library type like std::tuple.

Community
  • 1
  • 1
Ben Jones
  • 919
  • 1
  • 8
  • 22
  • The act of swapping them would instantly require them to be converted to an lvalue, which would mean you could use `std::swap`. – Zac Howland Feb 27 '14 at 20:08
  • I'm not sure I understand your comment, but when I tried calling std::swap with temporaries, clang told me that the overload taking lvalue references was not valid. – Ben Jones Feb 27 '14 at 20:11
  • 4
    Isn't this basically the same as assigning the rvalue to the lvalue-object? Because the temporary cannot be used afterwards anyway, it might as well be a direct (rvalue) assignment instead of a swap. – Excelcius Feb 27 '14 at 20:13
  • 3
    I need some justification for the statement "there are times when it's correct and desirable to swap rvalues (when temporary objects contain references...)", I think that you are mistaken. Like @Excelcius says, swapping rvalues seems to be semantically meaningless. – Casey Feb 27 '14 at 20:15
  • In my particular use case, the call to std::swap is outside my control (it's within the bowels of std::sort). – Ben Jones Feb 27 '14 at 20:15
  • @BenJones In order to pass temporaries to a function to be swapped, they must be reference-able, which means you must first have an lvalue. Swapping 2 items that are not reference-able is meaningless. – Zac Howland Feb 27 '14 at 20:19
  • Here's a live demo: http://ideone.com/hCIJcz The temporaries contain references. @ZacHowland by my reading, the referencable requirement is necessary to be ValueSwappable, which seems to be distinct from Swappable – Ben Jones Feb 27 '14 at 20:22
  • @BenJones The linked SO question and your live demo show that it doesn't make much sense to swap two temporaries this way, but I'm starting to understand how that could be a problem within sort when writing your own iterator. – Excelcius Feb 27 '14 at 20:28
  • @BenJones Using your own example, you have meaningless code. Even if you *could* swap them, both of them are gone as soon as they swap is done. In order to actually do something meaningful, you would have to first bind them to an lvalue. – Zac Howland Feb 27 '14 at 20:33
  • @ZacHowland I think the point is that `std::swap(f(), g())` should swap the state of `a` and `b` conceptually. – Yakk - Adam Nevraumont Feb 27 '14 at 20:36
  • 4
    You cannot `swap` tuples containing references: references fail to be Swappable. – Casey Feb 27 '14 at 20:36
  • 1
    @Yakk but in order for it to be meaningful, at least 1 of those parameters **must** be an lvalue ... otherwise it does nothing. And attempting to swap references breaks anyway as you cannot reassign a reference to a new location (they are not swappable by definition `§ 17.6.3.2`). – Zac Howland Feb 27 '14 at 20:41
  • @ZacHowland that makes sense, thanks for the explanation. I think the boost::range lib does something similar to what I want, so I'll poke around and see how they did it. – Ben Jones Feb 27 '14 at 20:45
  • I think the question he linked to (his own) shows the problem better as his ideone-code. But I agree that one way or another it will result in either a meaningless swap or a try to swap references, which cannot work. Not sure though. The code which requires swapping of rvalues may have a major design flaw somewhere else which is probably not that obvious. – Excelcius Feb 27 '14 at 20:46
  • Now, swapping an lvalue with an rvalue is meaningful (and can be implemented as a move-assign). If you have a situation where maybe one parameter is an rvalue, or maybe an lvalue, and you want to (conceptually) `swap`, it might be useful. – Yakk - Adam Nevraumont Feb 27 '14 at 20:49
  • @Yakk: Imagine you have an iterator where `operator*` returns a proxy (like `std::vector::iterator`). In this case, `swap(*first, *last);` makes complete sense, even though both the left and the right are temporaries. (Although both clang and G++ can sort a vector fine) – Mooing Duck Feb 27 '14 at 21:08
  • @MooingDuck Apparently there is a specialization/overload of `std::swap` for `std::vector::reference`? http://ideone.com/vt5iQs -- that makes sense, as reference-semantic objects (objects that wrap references) do not work well with a generic `std::swap` algorithm! The issue is that if you have a reference-semantics class `C`, then `auto x = (C)y;` is also a reference-semantics type, not a value-semantics type (which, if `C` was a real reference type, it would be). – Yakk - Adam Nevraumont Feb 27 '14 at 21:17
  • @Casey I don't quite see why references are not Swappable in the sense of [swappable.requirements]. That section talks of "An object `t`", which doesn't apply to references directly. But if we interpret that according to the "the object referred to by `t`" which is used later, I think it somehow makes sense and references are Swappable if the referenced type is. – dyp Feb 27 '14 at 21:18
  • @dyp I misspoke, I meant to say that *objects containing references* are not swappable due to the requirement of exchanging values. [This program demonstrates the point.](http://coliru.stacked-crooked.com/a/23b24ab37e8b5ca1) It's not impossible that I am misinterpreting "`t` has the value originally held by `u`". – Casey Feb 27 '14 at 21:41
  • @dyp `operator ==` is the golden standard for comparing values. I understand that the notion of value is poorly specified, but we'd like to at least be able to say that `operator ==`, if defined, returns `true` for objects with the same value. – Casey Feb 27 '14 at 21:53
  • @Casey Nevertheless, the "originally held" IMHO doesn't imply that you can compare it to a copy. Similarly, MoveConstructible doesn't imply CopyConstructible but also refers to a past value of the object. – dyp Feb 27 '14 at 21:55
  • In the absence of special handling, objects containing references - say `struct ref { int& r; }` - aren't assignable anyway, and will blow up `std::swap`. I think I'm flogging a dead horse here, and I don't have any ideas for improving the wording of 17.6.3.5, so I will let this argument go. Thanks to everyone who contributed ;) – Casey Feb 27 '14 at 22:10
  • Excellent question, some STL algorithms require a swap for r-values if the iterator produced an r-value. It is a valid discussion whether swap should word with r-values. The workaround I found to not deal with this question was to specialize `std::iter_swap` for these special iterators. I think that solve the problem for all STL algorithms. – alfC Nov 06 '16 at 05:18

2 Answers2

3

How about instead creating an lvalue_cast utility function that casts an rvalue to an lvalue:

#include <tuple>
#include <iostream>

template <class T>
T&
lvalue_cast(T&& t)
{
    return t;
}

int
main()
{
    int i = 1;
    int j = 2;
    swap(lvalue_cast(std::tie(i)), lvalue_cast(std::tie(j)));
    std::cout << i << '\n';
    std::cout << j << '\n';
}
Howard Hinnant
  • 206,506
  • 52
  • 449
  • 577
  • Because swapping references - and hence tuples containing references - is still UB. – Casey Feb 27 '14 at 21:06
  • @Casey: Somebody forgot to tell the committee that ([tuple.assign]/p3). The whole reason tuple copy assignment isn't defaulted is to handle the tuple case. – Howard Hinnant Feb 27 '14 at 21:11
  • 17.6.3.2/2 requires that the effect of `swap(t, u)` and `swap(u, t)` is that "the object referred to by `t` has the value originally held by `u` and the object referred to by `u` has the value originally held by `t`." [Does this program not demonstrate that requirement doesn't hold for tuples of references?](http://coliru.stacked-crooked.com/a/23b24ab37e8b5ca1) – Casey Feb 27 '14 at 21:30
  • 1
    How about renaming `lvalue_cast` to `copy`? That would be more in line with `move` ;) – fredoverflow Feb 27 '14 at 21:31
  • @Casey: Does my code compile for you and output 2, 1? – Howard Hinnant Feb 27 '14 at 21:33
  • Any warnings? Crank 'em all the way up. – Howard Hinnant Feb 27 '14 at 21:34
  • 1
    @HowardHinnant Are you implying that compilers warn for all possible incidences of undefined behavior? – Casey Feb 27 '14 at 21:35
  • My argument is that [the swappable requirement I cite above](http://stackoverflow.com/questions/22079128/specializing-stdswap-for-rvalues#comment33488074_22080328) is not met by objects that contain references, which would make it UB to pass references to such objects to `std::swap`. And *that* is where this argument breaks down for `std::tuple`: the `swap` found by ADL is not `std::swap`. I'll retract my earlier statement about tuples containing references, although I'm still unconvinced about objects containing references being swappable in general. – Casey Feb 27 '14 at 21:47
  • [tuple.swap] just says that the tuple swap should just call swap on each pair of elements. That algorithm will do the same thing whether the element is an object type or lvalue reference type. Well except that the in the lvalue reference case the swap will impact objects external to the tuple. – Howard Hinnant Feb 27 '14 at 21:52
  • @HowardHinnant: Which, surely, violates the semantics of `swap` that were quoted above – Lightness Races in Orbit Feb 28 '14 at 10:53
  • @fredoverflow, I would agree with the name `copy` if the signature were `template T const& copy(T&& t)`. (just as "move" doesn't move anything, "copy" wouldn't copy anything). Of course it wouldn't be useful for this. Maybe a name like `refer` or `evoke` would be more appropriate. I think this this could merit a blanket overload of `std::swap` for r-value references. (just like there is a blanket overload for `operator<<` https://en.cppreference.com/w/cpp/io/basic_ostream/operator_ltlt2). See my answer. – alfC Nov 07 '19 at 02:56
2

Some STL algorithms would seem to require a swap for r-values if the iterator produced an r-value (for example a temporary).

It is a valid discussion whether swap should work with r-values. I often see this problem because for "special" iterators, *it produces an r-value and sometimes you want to do the right things when "swapping".

The workaround I found to not deal with this question was to specialize std::iter_swap for these special iterators in the first place. I think that solve the problem for all STL algorithms.

namespace std{
void iter_swap(special_iterator it1, special_iterator it2){
   ... special swap of pointed values
}
}

I think this is a fundamental customization point that all permuting STL algorithm miss and lack.

(Of course if we are at the point of hacking the STL we can as well do namespace std{template<class T, ...enable_if T&& is r-value ref just in case...> void swap(T&& t1, T&& t2){swap(t1, t2);}. But it is even more risky.)

alfC
  • 14,261
  • 4
  • 67
  • 118