2015-12-09 2 views
3

Потенциально связанные статьи:Почему аргумент ссылки rvalue соответствует заданию const в разрешении перегрузки?

Для STL контейнера C, std::begin(C) и аналогичные функции доступа, включая std::data(C) (так как C++, 17), как предполагается, имеют такое же поведение от C::begin() и других соответствующих методов C. Тем не менее, я наблюдаю некоторое интересное поведение из-за деталей разрешения перегрузки с использованием ссылок lvalue/rvalue и константы.

DataType1 является int* как легко ожидалось. Также подтвердили с помощью Boost's type_id_with_cvr. const vector<int> дает int const* Здесь нет ничего удивительного.

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* 
... 

Я попытался std::data, который также может обрабатывать массивы и без STL контейнеров. Но он дает int const*. Аналогично, std::begin возвращает const итератор, хотя T не 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* 

Вопрос: Какова причина этого различия? Я ожидал, что C.data() и std::data(C) будут вести себя одинаково.


Некоторые мои исследования: Для того, чтобы получить int* для DataType3, T должны быть преобразованы в неконстантного Lvalue ссылочного типа в явном виде. Я пробовал declval, и он работал.

using DataType3 = decltype(std::data(std::declval<T&>())); // int* 

std::data обеспечивает две перегрузки:

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

В то время как разрешение перегруженных функций для std::data, T(), который является Неконстантный ссылка Rvalue, сравнивается с версией const T& вместо T& версии.

Было нелегко найти это конкретное правило разрешения перегрузки в стандарте (13.3, over.match). Было бы намного яснее, если бы кто-то мог указать точные правила для этой проблемы.

+0

Интересно. Для 'begin' /' end' это обычно не замечается, так как вы никогда не будете использовать их на rvalue. Вы скажете 'auto && __x = f(); auto it = begin (__ x); ', поэтому аргумент всегда является lvalue. С помощью 'data' можно было бы аналогичным образом утверждать, что данные без размера бесполезны, поэтому вам нужно также промежуточное значение lvalue. –

+0

Но, может быть, вы все равно должны подать библиотеку. –

ответ

2

Это поведение объясняется правилами разрешения перегрузки. Согласно стандарту 8.5.3/p5.2 Ссылки [dcl.init.ref], ссылки rvalue связываются с ссылками на константу lvalue. В этом примере:

std::data(T()) 

Вы предоставляете std::data RValue. Таким образом, из-за правил разрешения перегрузки перегрузка:

template <class _Cont> constexpr 
auto data(const _Cont& __c) -> decltype(__c.data()) { return __c.data(); } 

- лучшее совпадение.Следовательно, вы получаете const int*

Вы не можете привязать временную ссылку на константу lvalue.

+0

Спасибо. Я также понял это правило. Но я не совсем понимаю причины этого правила. Есть ли причина, по которой ссылка non-const lvalue не может быть привязана к rvalue? – minjang

+0

Тем не менее временный объект может быть изменен их функциями-членами. Я нашел это фундаментальное различие между членом и свободной функцией забавным. (см. мой ответ). Знаете ли вы, что стандарт говорит что-то о том, что функции-члены могут изменять временные рамки? – alfC

+0

Это испорчено. 'std :: data (T())' передает значение * rvalue * в 'data', а не ссылку rvalue. «это означало бы, что вы могли бы изменить временные объекты» - ну, это похоже на всю ссылку на ссылки rvalue, чтобы вы могли украсть мужество временных объектов, изменив их. –

2

Единственная линия, которая слегка удивляет, это using DataType1 = decltype(T().data()); // int*.

... но это все еще нормально, member функции могут быть вызваны на временные объекты без обработки как const. Это еще одно нетривиальное различие между функциями-членами и свободными функциями.

Например, в C++ 98 (предварительно RValue ссылках) не было возможности сделать std::ofstream("file.txt") << std::string("text"), потому что operator<< не является членом и временное рассматривается как const. Если бы operator<< были участником std::ofstream, это было бы возможно (и может даже иметь смысл). (Ситуация изменилась позже на C++ 11 с помощью ссылок rvalue, но точка все еще действительна).

Вот это пример:

#include<iostream> 
struct A{ 
    void f() const{std::cout << "const member" << std::endl;} 
    void f(){std::cout << "non const member" << std::endl;} 
}; 

void f(A const&){std::cout << "const function" << std::endl;} 
void f(A&){std::cout << "non const function" << std::endl;} 

int main(){ 
    A().f(); // prints "non const member" 
    f(A()); // prints "const function" 
} 

Поведение подвергается воздействию, когда объект временно сконструировать и использовать. Во всех остальных случаях, я могу себе представить, член f эквивалентен бесплатной функции f. (r-value эталон квалификации && - для членов и функций-- может дать вам более мелкозернистый контроль, но это не было частью вопроса.)