De acuerdo con ¿Qué son los valores, valores, valores, valores, valores y valores? y algunas otras explicaciones, entiendo que xvalue es la expresión que tiene identidad y se mueve con seguridad (o está marcada).

Algunos textos como this y esto dice que, si el tipo de retorno de una función f() es referencia de valor, entonces la expresión { {X1}} es xvalue. Por ejemplo:

int&& f() {
  return 1;
}

int main() {
  f(); // xvalue
  2; // prvalue
}

Mi confusión es que, debido a que el origen de f() es el literal 1, para mí f() no parece tener una identidad y, por lo tanto, no puedo entender cómo se convierte en xvalue. Si 1 tiene identidad, ¿por qué se dice que 2 no tiene identidad y es prvalue? ¿Prvalue de repente tiene "identidad" si se devuelve de una función como referencia de rvalue?

EDITAR

Se señaló que f() devuelve una referencia pendiente, pero espero que mi punto aún tenga sentido.

EDIT2

Bueno, después de leer los comentarios (muy útiles), ¿parece que probablemente no tiene sentido?

c++
1
akai 25 jun. 2020 a las 22:56

3 respuestas

La mejor respuesta

¿Prvalue de repente tiene "identidad" si se devuelve de una función como referencia de rvalue?

Si en realidad El estándar dice más o menos eso:

[conv.rval]

Un valor prva de tipo T puede convertirse en un valor x de tipo T. Esta conversión inicializa un objeto temporal ([class.temporary]) del tipo T del valor prudente al evaluar el valor prva con el objeto temporal como su objeto resultante, y produce un valor x que denota el objeto temporal.

Ese objeto temporal, mientras existe, ciertamente tiene "identidad". Por supuesto, el resultado de tal conversión ya no es un valor, por lo que quizás no deberíamos decir que el valor "obtiene una identidad". Tenga en cuenta que esto también funciona también debido a la materialización temporal:

(int&&)1; // This is different from f(), though, because that reference is dangling but I believe this one isn't (lifetime extension of a temporary by binding to a reference applies here but is suppressed for a return)

Tenga en cuenta que el operando de una instrucción return y lo que realmente se devuelve simplemente no tiene que ser lo mismo. Usted otorga un int prvalue, necesita un int xvalue, el return lo hace funcionar al materializar un temporal. No está obligado a fallar debido a la falta de coincidencia. Desafortunadamente, ese temporal se destruye inmediatamente cuando finaliza la instrucción return, dejando colgado el valor x, pero, en ese momento, entre la referencia devuelta que está vinculada y la temporal que se está destruyendo, sí, la referencia de valor realmente se refería a un objeto con identidad propia

Otros ejemplos de valores que se están materializando para que pueda vincular referencias a ellos:

int &&x = 1; // acts just like int x = 1 except for decltype and similar considerations
int const &y = 1; // ditto but const and also available in older C++ versions

// in some imaginary API
void install_controller(std::unique_ptr<controller> &&new_controller) {
     if(can_replace_controller()) current_controller = std::move(new_controller);
}
install_controller(std::make_unique<controller>("My Controller"));
// prvalue returned by std::make_unique materializes a temporary, binds to new_controller
// might be moved from, might not; in latter case new pointer (and thus object)
// is destroyed at full-expression end (at the semicolon after the function call)
// useful to take by reference so you can do something like
auto p = std::make_unique<controller>("Perseverant Controller");
while(p) { wait_for_something(); install_controller(std::move(p)); }

Otros ejemplos de return no son triviales:

double d(int x) { return x; }
// int lvalue given to return but double prvalue actually returned! the horror!

struct dangerous {
    dangerous(int x) { launch_missiles(); }
};
dangerous f() { return 1; }
// launch_missiles is called from within the return!

std::vector<std::string> init_data() {
    return {5, "Hello!"};
}
// now the operand of return isn't even a value/expression!
// also, in terms of temporaries, "Hello!" (char const[7] lvalue) decays to a
// char const* prvalue, converts to a std::string prvalue (initializing the
// parameter of std::string's constructor), and then that prvalue materializes
// into a temporary so that it can bind to the std::string const& parameter of
// std::vector<std::string>'s constructor
1
HTNW 26 jun. 2020 a las 00:21

Para ser honesto, encuentro que todo el concepto de "tener identidad" es algo discutible.

Así es como tiendo a pensarlo:

  • Un prvalue es una expresión que crea un objeto.

  • Un valor r es una expresión que denota un objeto temporal (o un objeto considerado como temporal, por ejemplo, porque era std::move d).

  • Un lvalue es una expresión que denota un objeto no temporal (o un objeto considerado no temporal).

Una llamada a int &&f() {...} no crea un nuevo objeto (al menos si ignoramos el cuerpo de la función, y solo miramos el mecanismo de llamada a la función en sí), por lo que el resultado no es un valor (pero obviamente es un valor , por lo tanto, también es un xvalue).

Una llamada a int f() {...}, por otro lado, crea incondicionalmente un objeto (el int temporal; sin importar el cuerpo de la función), por lo que es un valor.

0
HolyBlackCat 25 jun. 2020 a las 21:18

Aquí trato de resumir mi comprensión después de leer los comentarios dados.

El propósito completo de devolver una referencia de valor es usarla de alguna manera, por lo que no se considera devolver una referencia de valor que apunte a un objeto local de función, que ya no es válido cuando la función regresa (bueno, estoy seguro de que el comité considera esto, por supuesto, pero no como un uso previsto).

Como resultado, si tengo una función T&& f() { /.../ return val; }, se supone que val se ubica en algún lugar con su identidad incluso después de que f() regrese, de lo contrario, está colgando, lo cual es un simple error. Por lo tanto, la intención de que f() tenga una identidad, por lo que es un valor x, está justificada.

0
akai 25 jun. 2020 a las 20:57