Подтвердить что ты не робот

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

#include <iostream>

using namespace std;

template<typename T>
void f(T&&) { cout << "f(T&&)" << endl; }

template<typename T>
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.

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

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

4b9b3361

Ответ 1

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

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

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

Ответ 2

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

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

В С++ 11 от § 5 Expressions [expr] (стр. 84):

-

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

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

2

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

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

Ответ 3

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

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

Если первоначально значение prvalue имеет тип "cv T", где T является cv-unqualified non-class, non-array type, тип выражения до любого дальнейшего анализа доводится до T.

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

(акцент мой)

Всякий раз, когда выражение glvalue появляется как операнд оператора который ожидает значение для этого операнда, значение lvalue-to-rvalue, переменные-указатели или стандартные-преобразования указатели-указатели применяется для преобразования выражения в prvalue. [Примечание: Потому что cv-квалификаторы удаляются из типа выражения неклассов введите, когда выражение преобразуется в prvalue, значение lvalue выражение типа const int может, например, использоваться там, где prvalue требуется выражение типа int. - end note]

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

Ответ 4

Предыдущие ответы совершенно верны. Я просто хочу добавить потенциальную мотивацию, почему иногда бывает полезно возвращать объекты 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 data
}