Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

std::begin(X) vs. X.begin() for rvalues

For STL-containers, I think the consensus on std::begin is that "std::begin(c) is actually the same as c.begin()". However, according to cppreference, there is no overload for std::begin(C&& c), so if I call std::begin with an rvalue-reference, then the const&-version is called, giving me a C::const_iterator, which seems counter intuitive (why would I not be able to modify an rvalue container?) and disagrees with C::begin() && (which returns a C::iterator):

#include <unordered_map>
#include <type_traits>

using MyMap = std::unordered_map<int, int>;
using Iter = typename MyMap::iterator;
using ConstIter = typename MyMap::const_iterator;

static_assert(std::is_same_v<decltype(MyMap().begin()), Iter>);
static_assert(std::is_same_v<decltype(std::begin(MyMap())), Iter>); // <-- fails

static_assert(std::is_same_v<decltype(MyMap().begin()), ConstIter>); // <-- fails
static_assert(std::is_same_v<decltype(std::begin(MyMap())), ConstIter>);

(play with it here)

So I wonder, is this a bug/oversight in the standard/in gcc & clang or am I doing something wrong?

Edit: I am currently using this workaround

  template<class T> concept has_begin = requires (T t) { t.begin(); };

  template<class T>
  decltype(auto) my_begin(T&& x) {
    if constexpr(has_begin<std::remove_reference_t<T>>)
      return std::forward<T>(x).begin();
    else return std::begin(x);
  }

but it seems like a workaround for a bug in the standard, hence the question.

PS: Yes, I am aware that this is an uncommon use-case. Let me propose the following code as a (artificial) example that breaks:

template<class T>
decltype(auto) do_things(T&& in) {
    const auto _end = in.end();
    auto it = std::begin(std::forward<T>(in));
    while(it != _end) {
        it->second += it->first; // fails, even when passing non-const rvalues as 'in'
        ++it;
    }
    return std::forward<T>(in);
}

Now, one might say std::begin(std::forward<T>(in)) should just be std::begin(in) but I have some classes whose begin() && differs largely from their begin() & and I may pass them as in.

like image 254
igel Avatar asked Jun 21 '26 14:06

igel


1 Answers

Calling either on an rvalue container is likely a mistake. That container is going to cease to exist at the end of the full expression, and you have no way of referring to it to get an end iterator. std::begin(MyMap()), std::end(MyMap()) is not a valid range, those iterators refer to different objects.

If you have an rvalue reference, then you can use that to provide both ends of a range, but when doing so you have an lvalue.

auto && myMap = MyMap();
std::begin(myMap), std::end(myMap); // <- uses std::begin(MyMap&)
like image 96
Caleth Avatar answered Jun 23 '26 05:06

Caleth