operator - overload c++ example
¿Por qué los tipos primitivos y definidos por el usuario actúan de manera diferente cuando se devuelven como ''const'' desde una función? (4)
¿Por qué los tipos primitivos y definidos por el usuario actúan de manera diferente cuando se devuelven como ''const'' desde una función?
Porque la parte const
se elimina de los tipos primitivos devueltos por las funciones. Este es el por qué:
En C ++ 11 de § 5 Expressions [expr]
(p.84):
8
Siempre que una expresión glvalue aparece como un operando de un operador que espera un valor pr para ese operando, las conversiones estándar de lvalue-a-rvalue (4.1), de matriz a puntero (4.2) o de función a puntero (4.3) son aplicado para convertir la expresión en un valor pr. [Nota: como los calificadores cv se eliminan del tipo de una expresión de tipo no de clase cuando la expresión se convierte en un valor pr, una expresión lvalue de tipo const int puede, por ejemplo, usarse cuando una expresión prvalue de tipo int es requerido. -finalizar nota]
Y de manera similar, del § 5.2.3 Explicit type conversion (functional notation) [expr.type.conv]
( § 5.2.3 Explicit type conversion (functional notation) [expr.type.conv]
):
2
La expresión T (), donde T es un especificador de tipo simple o de tipo de letra para un tipo de objeto completo que no es de matriz o el tipo de vacío (posiblemente cv-calificado), crea un prvalue del tipo especificado, que es un valor inicializado ( 8.5; no se realiza inicialización para el caso void ()). [Nota: si T es un tipo no de clase que está calificado para CV, los criterios de cv se ignoran al determinar el tipo del prvalue resultante (3.10). -finalizar nota]
Lo que eso significa es que const int
prvalue devuelto por g2()
se trata efectivamente como int
.
#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.
}
La descripción del problema está incrustada en el código. Mi compilador es clang 5.0
.
Solo me pregunto:
¿Por qué C ++ trata los tipos incorporados y los tipos personalizados de manera diferente en tal caso?
Citas del estándar,
Si un prvalue tiene inicialmente el tipo "cv T", donde T es un tipo no-cv no calificado, que no es de clase, el tipo de expresión se ajusta a T antes de cualquier análisis posterior.
(énfasis mío)
Siempre que una expresión glvalue aparece como un operando de un operador que espera un prvalue para ese operando, las conversiones estándar lvalue-to-rvalue, array-to-pointer o function-to-pointer se aplican para convertir la expresión en un valor pr. [Nota: Debido a que los calificadores cv se eliminan del tipo de una expresión de tipo no de clase cuando la expresión se convierte a un valor pr, una expresión lvalue de tipo
const int
puede, por ejemplo, usarse cuando una expresión prvalue de tipoint
es requerido. - nota final]
Entonces para g2()
, int
es un tipo no de clase, y (el valor de retorno de) g2()
es una expresión de prvalue , entonces se elimina el calificador const
, por lo que el tipo de retorno no es const int
, sino int
. Es por eso que se llama f(T&&)
.
Las respuestas anteriores son perfectamente válidas. Solo quiero agregar una motivación potencial por la cual a veces puede ser útil devolver objetos const. En el siguiente ejemplo, la class A
brinda una vista de los datos internos de la class C
, que en algunos casos no serán modificables (Descargo de responsabilidad, por brevedad, algunas partes esenciales quedan fuera). También hay formas más fáciles de implementar este comportamiento:
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
}
No tengo una cita del estándar, pero cppreference confirma mis sospechas:
Un prvalue no de clase no de clase no puede ser calificado por cv. (Nota: una llamada a una función o expresión emitida puede dar como resultado un valor privalido de tipo no calificado por la clase cv, pero el cv-qualifier se elimina inmediatamente).
El const int
devuelto es solo un prvalue int
normal, y hace que la sobrecarga no const sea una mejor coincidencia que const
one.