Potentially related articles:
For a STL container C
, std::begin(C)
and similar access functions including std::data(C)
(since C++17) are supposed to have the same behavior of C::begin()
and the other corresponding C
's methods. However, I am observing some interesting behaviors due to the details of overload resolution involving lvalue/rvalue references and constness.
DataType1
is int*
as easily expected. Also, confirmed the by with Boost's type_id_with_cvr
. const vector<int>
gives int const*
No surprise here.
using T = vector<int>;
using DataType1 = decltype(T().data()); // int*
using CT = const T;
using DataType2 = decltype(CT().data()); // int const*
using boost::typeindex::type_id_with_cvr;
cout << type_id_with_cvr<DataType1>() << endl; // prints int*
...
I tried std::data
, which can also handle arrays and non-STL containers. But it yields int const*
. Similarly, std::begin
returns a const iterator, even though T
is not const.
using T = vector<int>;
using DataType3 = decltype(std::data(T())); // int const* Why ???
using CT = const T;
using DataType4 = decltype(std::data(CT())); // int const*
Question: What is the reason of this difference? I expected that C.data()
and std::data(C)
would behave in the same manner.
Some my research: In order to get int*
for DataType3
, T
must be converted to non-const lvalue reference type explicitly. I tried declval
, and it was working.
using DataType3 = decltype(std::data(std::declval<T&>())); // int*
std::data
provides two overloads:
template <class _Cont> constexpr
auto data(_Cont& __c) -> decltype(__c.data()) { return __c.data(); }
// non-const rvalue reference argument matches to this version.
template <class _Cont> constexpr
auto data(const _Cont& __c) -> decltype(__c.data()) { return __c.data(); }
While resolving overloaded functions for std::data
, T()
, which is non-const rvalue reference, is matched to the const T&
version instead of T&
version.
It was not easy to find this specific overload resolution rule in the standard (13.3, over.match). It'd be much clearer if someone could point the exact rules for this issue.
This behaviour is attributed to overload resolution rules. As per standard 8.5.3/p5.2 References [dcl.init.ref], rvalue references bind to const lvalue references. In this example:
std::data(T())
You provide to std::data
an rvalue. Thus, due to overload resolution rules the overload:
template <class _Cont> constexpr
auto data(const _Cont& __c) -> decltype(__c.data()) { return __c.data(); }
is a better match. Consequently you get const int*
You can't bind a temporary to a non-const lvalue reference.