对于 STL 容器,我认为的共识std::begin是“ ”。但是,根据,没有重载std::begin(C&& c),因此如果我std::begin使用右值引用调用,则会const&调用 -version,从而得到一个C::const_iterator,这似乎违反直觉(为什么我不能修改右值容器?)并且不同意C.begin() &&(返回一个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>);

所以我想知道,这是标准/ gcc 和 clang 中的错误/疏忽,还是我做错了什么?

编辑:我目前正在使用这个解决方法

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

但它似乎是标准中一个错误的解决方法,因此才有这个问题。

PS:是的,我知道这是一个不常见的用例。我建议使用以下代码作为(人为的)示例:

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

现在,有人可能会说std::begin(std::forward<T>(in))应该只是std::begin(in),但我有一些类begin() &&与他们的有很大不同begin() &,我可能会把它们当作in

5

  • 3
    在我看来,这两种都是错的。它应该是返回的std::move_iterator或类似的类型。


    – 


  • @HolyBlackCat 呃,不错,第一次听说std::move_iterator。这似乎正是我想要的!


    – 

  • 您还可以查看。我知道这std::ranges::begin解决了一些与std::begin左值/右值和 constness 有关的问题,但我不知道具体是什么。这可能是您所需要的。


    – 

  • do_things有点可疑。您正在转发两次,这意味着它可能已被移出。您应该在std::begin(in)那里,in始终是一个左值。


    – 


  • 你是对的,用户有责任不使用返回结果,以防他们传入的内容在begin() &&调用后变得无法使用,但这种情况可能发生也可能不发生……


    – 


最佳答案
1

在右值容器上调用任何一个都可能是错误的。该容器将在完整表达式的末尾不复存在,并且您无法引用它来获取结束迭代器。std::begin(MyMap()), std::end(MyMap())不是有效范围,这些迭代器引用不同的对象。

如果您有一个右值引用,那么您可以使用它来提供范围的两端,但是这样做时您就有一个左值。

auto && myMap = MyMap();
std::begin(myMap), std::end(myMap); // <- uses std::begin(MyMap&)

11

  • 但是有一个正确的用法:或者如果重载的std::begin(std::move(myMap))话就会是这样std::begin&&


    – 

  • @bolov 是的,但这意味着std::end(std::move(myMap))这并没有错,只是看起来很可疑。


    – 

  • “可能”并不意味着“必然”。事实上,我正在编写一个高度模板化的库,并且(出于复杂的原因)我调用了很多,当std::begin(std::forward<T>(x)),它会中断。xT&&


    – 

  • @igel 为什么对你不起作用std::begin(x)?也许你想要的是forward_iterator<T>(std::begin(x)),如果是右值,则将forward_iterator包装在其中,如果是左值,则不加改变地传递std::move_iteratorTT


    – 

  • 1
    如果您的traversals 始终是您控制的类,那么您可以使用std::forward<T>(x).begin().existsstd::begin因为数组没有成员函数。


    –