Intereting Posts

Почему примитивные и пользовательские типы действуют по-разному, когда они возвращаются как функция «const» из функции?

#include  using namespace std; template void f(T&&) { cout << "f(T&&)" << endl; } template void f(const T&&) { cout << "f(const T&&)" << endl; } struct A {}; const A g1() { return {}; } const int g2() { return {}; } int main() { f(g1()); // outputs "f(const T&&)" as expected. f(g2()); // outputs "f(T&&)" not as expected. } 

Описание проблемы встроено в код. Мой компилятор clang 5.0 .

Мне просто интересно:

Почему C ++ обрабатывает встроенные типы и пользовательские типы по-разному в таком случае?

У меня нет цитаты из стандарта, но cppreference подтверждает мои подозрения:

Неclassическое присвоение не-массива не может быть cv-квалифицированным. (Примечание: вызов функции или выражение выражений может привести к присвоению classа, не относящегося к classу cv, но cv-квалификатор немедленно удаляется.)

Возвращаемый const int является просто нормальным int prvalue и делает неконстантную перегрузку лучшим совпадением, чем const .

Почему примитивные и пользовательские типы действуют по-разному, когда они возвращаются как функция «const» из функции?

Поскольку const part удаляется из примитивных типов, возвращаемых из функций. Вот почему:

В C ++ 11 из § 5 Expressions [expr] (стр. 84):

8

Всякий раз, когда выражение glvalue появляется как операнд оператора, ожидающего prvalue для этого операнда, стандартные значения преобразования lvalue-to-rvalue (4.1), array-to-pointer (4.2) или функции-to-pointer (4.3) применяется для преобразования выражения в prvalue. [Примечание: поскольку cv-quali fiers удаляются из типа выражения типа non-class, когда выражение преобразуется в prvalue, выражение lvalue типа const int может, например, использоваться, когда выражение prvalue типа int необходимо. -End note]

И аналогично из § 5.2.3 Explicit type conversion (functional notation) [expr.type.conv] (стр. 95):

2

Выражение T (), где T – спецификатор простого типа или typename-спецификатор для не-массивного полного типа объекта или (возможно, cv-qualified) тип void, создает prvalue специфицированного типа, который является инициализированным значением ( 8.5, инициализация не выполняется для случая void ()). [Примечание: если T является неclassовым типом, который является квазиclassифицированным, cv-quali fiers игнорируются при определении типа полученного prvalue (3.10). -End note]

Это означает, что const int prvalue, возвращаемое g2() , эффективно обрабатывается как int .

Цитаты из стандарта,

§8 / 6 Выражения [expr]

Если изначальное значение prvalue имеет тип «cv T», где T является неквалифицированным неclassифицированным, не-массивным типом, тип выражения до тех пор, пока не будет проведен анализ.

и §8 / 9 Выражения [expr]

(акцент мой)

Всякий раз, когда выражение glvalue появляется как операнд оператора, ожидающего prvalue для этого операнда, стандартные преобразования преобразования lvalue-to-rvalue, array-to-pointer или function-to-pointer применяются для преобразования выражения в prvalue. [Примечание. Поскольку cv-квалификаторы удаляются из типа выражения типа non-class, когда выражение преобразуется в prvalue, выражение lvalue типа const int может, например, использоваться, когда выражение prvalue типа int необходимо. – конечная нота]

Таким образом, для g2() int является неclassовым типом, и (возвращаемое значение) g2() является выражением prvalue , тогда определитель const удаляется, поэтому возвращаемый тип не const int , а int . Вот почему вызывается f(T&&) .

Предыдущие ответы совершенно верны. Я просто хочу добавить потенциальную мотивацию, почему иногда бывает полезно возвращать объекты const. В следующем примере class A дает представление о внутренних данных из class C , которые в некоторых случаях не могут быть модифицируемыми (Отказ от ответственности, для краткости некоторые существенные части не учитываются), также возможны более простые способы реализации этого поведения):

 class A { int *data; friend class C; // allow C to call private constructor A(int* x) : data(x) {} static int* clone(int*) { return 0; /* should actually clone data, with reference counting, etc */ } public: // copy constructor of A clones the data A(const A& other) : data(clone(other.data)) {} // accessor operators: const int& operator[](int idx) const { return data[idx]; } // allows modifying data int& operator[](int idx) { return data[idx]; } }; class C { int* internal_data; public: C() : internal_data(new int[4]) {} // actually, requires proper implementation of destructor, copy-constructor and operator= // Making A const prohibits callers of this method to modify internal data of C: const A getData() const { return A(internal_data); } // returning a non-const A allows modifying internal data: A getData() { return A(internal_data); } }; int main() { C c1; const C c2; c1.getData()[0] = 1; // ok, modifies value in c1 int x = c2.getData()[0]; // ok, reads value from c2 // c2.getData()[0] = 2; // fails, tries to modify data from c2 A a = c2.getData(); // ok, calls copy constructor of A a[0] = 2; // ok, works on a copy of c2's data }