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.
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&)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With