template c++ c++11 templates g++ template-aliases

c++ - template - ¿Por qué la plantilla de alias da una declaración conflictiva?



c++ using (1)

El puerto de un código C ++ 11 de Clang a g ++

template<class T> using value_t = typename T::value_type; template<class> struct S { using value_type = int; static value_type const C = 0; }; template<class T> value_t<S<T>> // gcc error, typename S<T>::value_type does work const S<T>::C; int main() { static_assert(S<int>::C == 0, ""); }

da un comportamiento diferente para Clang (versiones 3.1 a través del tronco SVN) versus para cualquier versión de g ++. Para este último recibo errores como este

prog.cc:13:13: error: conflicting declaration ''value_t<S<T> > S< <template-parameter-1-1> >::C'' const S<T>::C; ^ prog.cc:8:29: note: previous declaration as ''const value_type S< <template-parameter-1-1> >::C'' static value_type const C = 0; ^ prog.cc:13:13: error: declaration of ''const value_type S< <template-parameter-1-1> >::C'' outside of class is not definition [-fpermissive] const S<T>::C;

Si en lugar del alias de la plantilla value_t<S<T>> utilizo el tipo de typename S<T>::value_type completo typename S<T>::value_type entonces g ++ también funciona .

Pregunta : ¿no se supone que los alias de las plantillas son completamente intercambiables con su expresión subyacente? ¿Es esto un error de g ++?

Actualización : Visual C ++ también acepta la plantilla de alias en la definición fuera de clase.


El problema se basa en SFINAE. Si reescribe su función de miembro para que sea value_t<S<T>> , como la declaración externa, entonces GCC la compilará felizmente:

template<class T> struct S { using value_type = int; static const value_t<S<T>> C = 0; }; template<class T> const value_t<S<T>> S<T>::C;

Porque la expresión ahora es funcionalmente equivalente. Cosas como la falla de sustitución entran en juego en las plantillas alias, pero como puede ver, la función miembro value_type const C no tiene el mismo "prototipo" que value_t<S<T>> const S<T>::C El primero no tiene que realizar SFINAE, mientras que el segundo lo requiere. Entonces, claramente, ambas declaraciones tienen una funcionalidad diferente, por lo tanto, la rabieta de GCC.

Curiosamente, Clang lo compila sin un signo de anormalidad. Supongo que sucede que el orden de los análisis de Clang se invierte, en comparación con los de GCC. Una vez que la expresión de alias-plantilla se resuelve y está bien (es decir, está bien formada), clang compara ambas declaraciones y comprueba que son equivalentes (que en este caso lo son, dado que ambas expresiones se resuelven en value_type ).

Ahora, ¿cuál es correcto de los ojos de la norma? Aún no se ha resuelto si consideramos el SFNIAE de alias-template como parte de la funcionalidad de su declaración. Citando eel.is/c++draft/temp.alias#2 :

Cuando un ID de plantilla se refiere a la especialización de una plantilla de alias, es equivalente al tipo asociado obtenido mediante la sustitución de sus argumentos de plantilla por los parámetros de plantilla en el id tipo de la plantilla de alias.

En otras palabras, estos dos son equivalentes:

template<class T> struct Alloc { /* ... */ }; template<class T> using Vec = vector<T, Alloc<T>>; Vec<int> v; vector<int, Alloc<int>> u;

Vec<int> y vector<int, Alloc<int>> son tipos equivalentes, porque después de que se realiza la sustitución, ambos tipos terminan siendo vector<int, Alloc<int>> . Tenga en cuenta cómo "después de la sustitución" significa que la equivalencia solo se verifica una vez que todos los argumentos de la plantilla se reemplazan con los parámetros de la plantilla. Es decir, la comparación comienza cuando T en el vector<T, Alloc<T>> se reemplaza por int de Vec<int> . Tal vez eso es lo que Clang está haciendo con value_t<S<T>> ? Pero luego está la siguiente cita de [temp.alias]/3 :

Sin embargo, si el ID de la plantilla es dependiente, la subsiguiente sustitución del argumento de la plantilla aún se aplica a la plantilla-id. [Ejemplo:

template<typename...> using void_t = void; template<typename T> void_t<typename T::foo> f(); f<int>(); // error, int does not have a nested type foo

- ejemplo final]

Aquí está el problema: la expresión tiene que estar bien formada, por lo que el compilador debe verificar si la sustitución es correcta. Cuando hay una dependencia para realizar la sustitución del argumento de la plantilla (por ejemplo, typename T::foo ), la funcionalidad de toda la expresión cambia y la definición de "equivalencia" es diferente. Por ejemplo, el siguiente código no se compilará (GCC y Clang):

struct X { template <typename T> auto foo(T) -> std::enable_if_t<sizeof(T) == 4>; }; template <typename T> auto X::foo(T) -> void {}

Porque el prototipo del foo externo es funcionalmente diferente del interno. Hacer auto X::foo(T) -> std::enable_if_t<sizeof(T) == 4> hace que el código compile bien. Es así porque el tipo de retorno de foo es una expresión que depende del resultado de sizeof(T) == 4 , por lo que después de la sustitución de la plantilla, su prototipo podría ser diferente de cada instancia de la misma. Mientras que el tipo de devolución auto X::foo(T) -> void nunca es diferente, lo que entra en conflicto con la declaración dentro de X Este es el mismo problema que está sucediendo con su código. Entonces GCC parece ser correcto en este caso.