c++ c++11 language-design rvalue-reference forwarding-reference

c++ - Sintaxis para referencias universales



c++11 language-design (2)

¿Por qué las referencias universales usan la misma sintaxis que las referencias rvalue? ¿No es eso una fuente innecesaria de confusión? ¿Alguna vez el comité consideró sintaxis alternativas ...

Sí, es confuso, IMO (No estoy de acuerdo con @JonathanWakely aquí). Recuerdo que durante una discusión informal (almuerzo, creo) sobre el diseño inicial de la función general, hablamos sobre diferentes anotaciones (Howard Hinnant y Dave Abrahams estaban allí presentando su idea y los chicos de EDG estaban dando su opinión sobre cómo se podría ajustar en el lenguaje central, esto es anterior a N1377). Creo que recuerdo &? y &|&& fueron considerados, pero todo esto fue verbal; No estoy al tanto de que se hayan tomado notas de la reunión (pero creo que esto es también cuando John sugirió el uso de && para las referencias de valor de r). Esas fueron las primeras etapas del diseño, sin embargo, y hubo muchos problemas semánticos fundamentales que considerar en ese momento. (Por ejemplo, durante la misma discusión del almuerzo, también planteamos la posibilidad de no tener dos tipos de referencias, sino tener dos tipos de parámetros de referencia).

Un aspecto más reciente de la confusión que esto causa se encuentra en la característica C ++ 17 de "deducción de argumento de plantilla de clase" (P0099R3). Allí se forma una firma de plantilla de función transformando la firma de constructores y plantillas de constructor. Para algo como:

template<typename T> struct S { S(T&&); };

una firma de plantilla de función

template<typename T> auto S(T&&)->S<T>;

está formado para usar para la deducción de una declaración como

int i = 42; S s = i; // Deduce S<int> or S<int&>?

Deducir T = int& aquí sería contra-intuitivo. Así que tenemos que agregar una "regla de deducción especial para deshabilitar la regla de deducción especial" en esta circunstancia :-(

Esta es una referencia de valor real:

void foo(int&& a);

No se une a lvalues:

int i = 42; foo(i); // error

Esta es una referencia universal:

template<typename T> void bar(T&& b);

Se une a rvalues ​​y también se une a lvalues:

bar(i); // okay

Esta es una referencia de valor real:

template<typename T> struct X { void baz(T&& c); };

No se une a lvalues:

X<int> x; x.baz(i); // error

¿Por qué las referencias universales usan la misma sintaxis que las referencias rvalue? ¿No es eso una fuente innecesaria de confusión? ¿Alguna vez el comité consideró sintaxis alternativas como T&&& , T&* , T@ o T&42 (es broma en la última)? Si es así, ¿cuáles fueron las razones para rechazar sintaxis alternativas?


Una referencia universal como T&& puede deducir que T es un " tipo de objeto " o un " tipo de referencia "

En su ejemplo, puede deducir T como int cuando pasa un valor r, entonces el parámetro de función es int&& , o puede deducir T como int& cuando pasa un lvalue, en cuyo caso el parámetro de función es int& (porque las reglas de colapso de referencia dicen std::add_rvalue_reference<int&>::type es solo int& )

Si T no se deduce mediante la llamada a la función (como en su ejemplo de X::baz ), entonces no se puede deducir a int& , por lo que la referencia no es una referencia universal.

Así que en mi humilde opinión no hay necesidad de nueva sintaxis, encaja muy bien en la deducción de argumento de plantilla y las reglas de colapso de referencia, con la pequeña modificación de que un parámetro de plantilla puede deducirse como un tipo de referencia (donde en C ++ 03 un parámetro de plantilla de función de tipo T o T& siempre deducirían T como un tipo de objeto.)

Esta semántica y esta sintaxis se propusieron desde el principio cuando las referencias de valores y un ajuste al argumento de las reglas de deducción se propusieron como la solución al problema de reenvío, ver N1385 . El uso de esta sintaxis para proporcionar un reenvío perfecto se propuso en paralelo con la propuesta de referencias rvalue para los propósitos de la semántica de movimientos: N1377 estaba en el mismo correo que N1385. No creo que alguna vez se haya propuesto seriamente una sintaxis alternativa.

En mi humilde opinión, una sintaxis alternativa en realidad sería más confusa de todos modos. Si tuvieras la template<typename T> void bar(T&@) como sintaxis para una referencia universal, pero con la misma semántica que tenemos hoy, cuando llames a bar(i) el parámetro de la plantilla T podría deducirse como int& or int y el parámetro de función sería de tipo int& o int&& ... ninguno de los cuales es " T&@ " (cualquiera que sea ese tipo). De modo que tendría gramática en el lenguaje para un declarante T&@ que no es un tipo que pueda existe, porque en realidad siempre se refiere a algún otro tipo, ya sea int& o int&& .

Al menos con la sintaxis que tenemos el tipo T&& es un tipo real, y las reglas de colapso de referencia no son específicas para plantillas de funciones que usan referencias universales, son completamente consistentes con el resto del sistema de tipo fuera de las plantillas:

struct A {} a; typedef A& T; T&& ref = a; // T&& == A&

O equivalente:

struct A {} a; typedef A& T; std::add_rvalue_reference<T>::type ref = a; // type == A&

Cuando T es un tipo de referencia lvalue, T&& también. No creo que se necesite una nueva sintaxis, las reglas realmente no son tan complicadas ni confusas.