7

Here's a link to relevant code:

#include <iostream>
#include <string>
#include <vector>
#include <type_traits>

int main()
{
  std::vector<int> v{1, 2, 3, 4, 5};
  auto iter = begin(std::move(v));
  if(std::is_const<typename std::remove_reference<decltype(*iter)>::type>::value)
    std::cout<<"is const\n";
  return 0;
}

http://coliru.stacked-crooked.com/a/253c6373befe8e50

I ran into this behavior because of a declval<Container>() in a decltype expression with std::begin. Both gcc and clang return iterators which yield const references when dereferenced. It probably makes sense since r-value references usually bind to expiring objects that you don't want to mutate. However, I could not find any documentation on this to determine whether it's mandated by the standard. I couldn't find any relevant overloads of begin() or ref-qualified overloads of Container::begin().

Update: The answers clarified what's happening but the interactions can be subtle as demonstrated below:

#include <iostream>
#include <string>
#include <vector>
#include <type_traits>

int main()
{
  if(std::is_const<typename std::remove_reference<decltype(*begin(std::declval<std::vector<std::string>>()))>::type>::value)
    std::cout<<"(a) is const\n";
  if(!std::is_const<typename std::remove_reference<decltype(*std::declval<std::vector<std::string>>().begin())>::type>::value)
    std::cout<<"(b) is not const\n";
  if(!std::is_const<typename std::remove_reference<decltype(*begin(std::declval<std::vector<std::string>&>()))>::type>::value)
    std::cout<<"(c) is not const\n";
  return 0;
}

http://coliru.stacked-crooked.com/a/15c17b288f8d69bd

Naively, you wouldn't expect different results for (a) and (b) when ::begin is just defined in terms of calling vector::begin. However the absence of std::begin overloads that take non-const r-value reference and return iterator (or ref-qualified vector::begin overloads which return const_iterator) cause exactly that to happen.

L. F.
  • 19,445
  • 8
  • 48
  • 82
authentec
  • 153
  • 6

2 Answers2

8

As you can see in http://en.cppreference.com/w/cpp/iterator/begin the interesting overloads are:

template<class C> auto begin(C& c) -> decltype(c.begin());
template<class C> auto begin(const C& c) -> decltype(c.begin());

and std::vector<int>&& can only bind to the second overload (so returns const_iterator).

Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • That makes sense but it's a bit surprising that `decltype(*begin(declval>()))` is a const std::string& when neither the vector nor string is const. If vector::begin were used instead the result would be string&! With ::begin you'd need to pass vector& to declval to avoid getting const string&. [This link](http://coliru.stacked-crooked.com/a/15c17b288f8d69bd) illustrates the subtlety. – authentec Sep 30 '16 at 19:48
  • That should have been std::begin instead of ::begin above. – authentec Sep 30 '16 at 20:57
7

Let's try to analyze what happens, step by step:

  1. You're calling std::begin(std::vector<int>&&), but std::begin has no overload that takes an rvalue:

    template< class C > 
    auto begin( C& c ) -> decltype(c.begin());
    
    template< class C > 
    auto begin( const C& c ) -> decltype(c.begin());
    

  1. Due to reference collapsing, a temporary (xvalue) will only bind to a const lvalue reference:

    If you call Fwd with an xvalue, we again get Type&& as the type of v. This will not allow you to call a function that takes a non-const lvalue, as an xvalue cannot bind to a non-const lvalue reference. It can bind to a const lvalue reference, so if Call used a const&, we could call Fwd with an xvalue.

    (From the linked answer).


  1. Therefore, the

     template<class C> auto begin(const C& c) -> decltype(c.begin());
    

    overload is being called, which returns a const iterator.

    Why?

    Because std::begin(v) calls v.begin(), which returns a const_iterator when called on const instances of std::vector.

Community
  • 1
  • 1
Vittorio Romeo
  • 90,666
  • 33
  • 258
  • 416