Considere el siguiente programa:

#include<iostream>
using namespace std;

struct S
{
    S() = default;
    S(const S& other) = delete;
    S(S&& other) = delete;
    int i;
};

S nakedBrace()
{
     return {}; // no S constructed here?
}

S typedBrace()
{
     return S{};
}

int main()
{
    // produce an observable effect.
    cout << nakedBrace().i << endl; // ok
    cout << typedBrace().i << endl; // error: deleted move ctor
}

Sesión de muestra:

$ g++ -Wall -std=c++14 -o no-copy-ctor no-copy-ctor.cpp
no-copy-ctor.cpp: In function 'S typedBrace()':
no-copy-ctor.cpp:19:12: error: use of deleted function 'S::S(S&&)'
   return S{};
            ^
no-copy-ctor.cpp:8:5: note: declared here
     S(S&& other) = delete;

Me asombra que gcc acepte nakedBrace(). Pensé que conceptualmente las dos funciones son equivalentes: se construye y se devuelve un S temporal. La elisión de la copia puede realizarse o no, pero el ctor de mover o copiar (ambos se eliminan aquí) aún debe ser accesible, como lo exige la norma (12.8 / 32).

¿Significa eso que nakedBrace() nunca construye una S? ¿O lo hace, pero directamente en el valor de retorno con inicialización de llaves, de modo que no se requiera conceptualmente ningún movimiento de copia / ctor?

6
Peter - Reinstate Monica 27 ene. 2016 a las 18:35

2 respuestas

La mejor respuesta

Este es el comportamiento estándar.

N4140 [stmt.return]/2: [...] Una declaración de retorno con una lista de inicialización entre llaves inicializa el objeto o referencia a ser devuelta desde la función por inicialización de lista de copias (8.5.4) desde el inicializador especificado lista. [...]

Esto significa que las inicializaciones realizadas por nakedBrace y typedBrace son equivalentes a estas:

S nakedBrace = {}; //calls default constructor
S typedBrace = S{}; //calls default, then copy constructor (likely elided)
4
TartanLlama 27 ene. 2016 a las 15:44

[stmt.return] / 2 ... Una declaración de retorno con una expresión de tipo no vacío se puede usar solo en funciones que devuelven un valor; el valor de la expresión se devuelve al llamador de la función. El valor de la expresión se convierte implícitamente al tipo de retorno de la función en la que aparece. Una declaración de retorno puede involucrar la construcción y copia o movimiento de un objeto temporal (12.2) ... Una declaración de retorno con una lista-de-inicio-entre corchetes inicializa el objeto o referencia a ser devuelto por la función copy-list-initialization (8.5.4) de la lista de inicializadores especificada.

[class.temporary] / 1 Los temporales del tipo de clase se crean en varios contextos: ... devolviendo un prvalue (6.6.3) ...

Así que sí, por lo que puedo decir, hay una diferencia semántica. typedBrace evalúa una expresión S{}, que produce un prvalue de tipo S, luego intenta copiar-construir su valor de retorno a partir de esa expresión. nakedBrace en su lugar, construye su valor de retorno directamente desde la lista de inicio-braced.

Es la misma situación que con S s{}; (funciona) vs S s = S{}; (no funciona), solo que oscurecida un poco por un nivel de indirección.

1
Community 20 jun. 2020 a las 09:12