tipos retorno prototipos procedimientos predefinidas parametros lenguaje funciones creacion con c++ c++11 templates language-lawyer

c++ - retorno - El tipo dependiente o argumento en decltype en la definición de la función no se compila cuando se declara sin decltype



tipos de funciones en c (1)

Porque cuando hay un parámetro de plantilla involucrado, decltype devuelve un tipo dependiente decltype acuerdo con el estándar, ver a continuación. Si no hay un parámetro de plantilla, se resuelve como un size_t obvio de size_t . Entonces, en este caso, debe elegir si la declaración y la definición tienen una expresión independiente (por ejemplo, size_t/decltype(sizeof(int)) ), como un tipo de retorno, o ambas tienen expresión dependiente (por ejemplo, decltype(sizeof(T)) ) , que se resolvió con un tipo dependiente único y se consideró equivalente, si sus expresiones son equivalentes (ver a continuación).

En esta publicación, estoy usando el borrador estándar de C ++ N3337 .

§ 7.1.6.2 [dcl.type.simpl]

¶ 4

El tipo denotado por decltype (e) se define de la siguiente manera: - si e es una expresión id no aparentezada o acceso de miembro de clase no apareado (5.2.5), decltype (e) es el tipo de la entidad nombrada por e. Si no existe tal entidad, o si e nombra un conjunto de funciones sobrecargadas, el programa está mal formado;

- de lo contrario, si e es un valor x, decltype (e) es T &&, donde T es el tipo de e;

- de lo contrario, si e es un valor l, decltype (e) es T &, donde T es el tipo de e;

- De lo contrario, decltype (e) es el tipo de e .

Esto explica qué es decltype(sizeof(int)) . Pero para decltype(sizeof(T)) hay otra sección que explica de qué se trata.

§ 14.4 [tipo de temperatura]

¶ 2

Si una expresión e implica un parámetro de plantilla, decltype (e) denota un tipo dependiente único . Dos de tales especificadores de decltype se refieren al mismo tipo solo si sus expresiones son equivalentes (14.5.6.1). [Nota: sin embargo, puede ser un alias, por ejemplo, por un typedef-name. - nota final]

En Clang LLVM fuentes versión 3.9 en el archivo lib/AST/Type.cpp

DecltypeType::DecltypeType(Expr *E, QualType underlyingType, QualType can) // C++11 [temp.type]p2: "If an expression e involves a template parameter, // decltype(e) denotes a unique dependent type." Hence a decltype type is // type-dependent even if its expression is only instantiation-dependent. : Type(Decltype, can, E->isInstantiationDependent(), E->isInstantiationDependent(), E->getType()->isVariablyModifiedType(), E->containsUnexpandedParameterPack()),

La frase importante comienza como "De ahí un decltype ...". Nuevamente aclara la situación.

De nuevo en Clang fuentes versión 3.9 en el archivo lib/AST/ASTContext.cpp

QualType ASTContext::getDecltypeType(Expr *e, QualType UnderlyingType) const { DecltypeType *dt; // C++11 [temp.type]p2: // If an expression e involves a template parameter, decltype(e) denotes a // unique dependent type. Two such decltype-specifiers refer to the same // type only if their expressions are equivalent (14.5.6.1). if (e->isInstantiationDependent()) { llvm::FoldingSetNodeID ID; DependentDecltypeType::Profile(ID, *this, e); void *InsertPos = nullptr; DependentDecltypeType *Canon = DependentDecltypeTypes.FindNodeOrInsertPos(ID, InsertPos); if (!Canon) { // Build a new, canonical typeof(expr) type. Canon = new (*this, TypeAlignment) DependentDecltypeType(*this, e); DependentDecltypeTypes.InsertNode(Canon, InsertPos); } dt = new (*this, TypeAlignment) DecltypeType(e, UnderlyingType, QualType((DecltypeType *)Canon, 0)); } else { dt = new (*this, TypeAlignment) DecltypeType(e, UnderlyingType, getCanonicalType(UnderlyingType)); } Types.push_back(dt); return QualType(dt, 0); }

Entonces, ves que Clang reúne y selecciona esos tipos dependientes únicos de decltype en / desde un conjunto especial.

¿Por qué el compilador es tan estúpido que no ve que la expresión de decltype es sizeof(T) que siempre es size_t ? Sí, esto es obvio para un lector humano. Pero cuando diseñas e implementas reglas gramaticales y semánticas formales, especialmente para lenguajes tan complicados como C ++, tienes que agrupar los problemas y definir las reglas para ellos, en lugar de solo presentar una regla para cada problema en particular, en este último. forma en que simplemente no podrá moverse con su diseño de lenguaje / compilador. Lo mismo aquí no hay una regla justa: si decltype tiene una expresión de llamada de función que no necesita ninguna resolución de parámetros de plantilla, resuelva el tipo de decltype al tipo de retorno de la función. Hay más que eso, hay tantos casos que necesita cubrir, que se le ocurre una regla más genérica, como la citada anteriormente del estándar ( 14.4[2] ).

Además, un caso no obvio similar con auto , decltype(auto) encontrado por AndyG en C ++ - 14 (N4296, § 7.1.6.4 [dcl.spec.auto] 12/13):

§ 7.1.6.4 [dcl.spec.auto]

¶ 13

Las redeclaraciones o especializaciones de una función o plantilla de función con un tipo de devolución declarada que utiliza un tipo de marcador de posición también deben usar ese marcador de posición, no un tipo deducido. [Ejemplo:

auto f(); auto f() { return 42; } // return type is int auto f(); // OK int f(); // error, cannot be overloaded with auto f() decltype(auto) f(); // error, auto and decltype(auto) don’t match

Cambios en C ++ 17, Número de documento> = N4582

El cambio en el borrador estándar N4582 de marzo de 2016 (gracias a bogdan) generaliza la afirmación:

§ 17.4 (antiguo § 14.4) [temp.tipo]

¶ 2

Si una expresión e depende del tipo (17.6.2.2) , decltype (e) denota un tipo dependiente único. Dos de tales especificadores de decltype se refieren al mismo tipo solo si sus expresiones son equivalentes (17.5.6.1). [Nota: sin embargo, tal tipo puede ser un alias, por ejemplo, por un typedef-name. - nota final]

Este cambio lleva a otra sección que describe la expresión dependiente de tipo que parece bastante extraña para nuestro caso particular.

§ 17.6.2.2 [temp.dep.expr] (antiguo § 14.6.2.2)

¶ 4

Las expresiones de los siguientes formularios nunca dependen del tipo de letra (porque el tipo de expresión no puede ser dependiente):

... sizeof ( type-id ) ...

Hay más secciones sobre expresiones dependientes del valor donde sizeof puede depender del valor si el type-id depende. No hay relación entre la expresión dependiente del valor y decltype . Después de pensar un poco, no encontré ninguna razón por la cual decltype(sizeof(T)) no deba o no pueda resolverse en size_t . Y supongo que fue un cambio bastante astuto ("implica un parámetro de plantilla" a "dependiente del tipo") en el estándar al que los desarrolladores de compiladores no prestaron mucha atención (tal vez abrumado por muchos otros cambios, tal vez no pensó que podría de hecho, cambia algo, solo una simple mejora en la formulación). El cambio tiene sentido, porque sizeof(T) no depende del tipo, depende del valor. decltype(e) es un operando no evaluado, es decir, no le importa el valor, solo sobre el tipo. Es por eso que decltype devuelve un tipo único solo cuando e depende del tipo. sizeof(e) puede ser solo dependiente del valor.

-std=c++1z el código con clang 5, gcc 8 -std=c++1z - el mismo resultado: error. Fui más allá y probé este código:

template <typename> struct Cls { static std::size_t f(); }; template <typename T> decltype(sizeof(sizeof(T))) Cls<T>::f() { return 0; }

Se dio el mismo error, incluso ese sizeof(sizeof(T)) no depende del tipo o del valor (ver esta publicación ). Esto me da una razón para suponer que los compiladores están trabajando en una forma antigua de C ++ - 11/14 estándar (es decir, "involucra un parámetro de plantilla") como en el fragmento fuente anterior de la fuente 3.9 de clac (puedo verificar que el último clang 5.0 tiene las mismas líneas, no ha encontrado nada relacionado con este nuevo cambio en el estándar), pero no depende del tipo.

He estado jugando con tipos de retorno deducidos en definiciones que se resuelven con el mismo tipo que la declaración. Esto funciona:

template <typename> struct Cls { static std::size_t f(); }; template <typename T> decltype(sizeof(int)) Cls<T>::f() { return 0; }

Pero si cambio la definición a algo que debería ser equivalente reemplazando sizeof(int) con sizeof(T) , falla

template <typename T> decltype(sizeof(T)) Cls<T>::f() { return 0; }

error de gcc (clang es casi idéntico):

error: prototype for ‘decltype (sizeof (T)) Cls<T>::f()’ does not match any in class ‘Cls<T>’ decltype(sizeof(T)) Cls<T>::f() { return 0; } ^~~~~~ so.cpp:4:24: error: candidate is: static std::size_t Cls<T>::f() static std::size_t f(); ^

El mismo problema surge con los tipos de parámetros de función:

template <typename> struct Cls { static void f(std::size_t); }; template <typename T> void Cls<T>::f(decltype(sizeof(T))) { } // sizeof(int) works instead

Aún más extraño, si la declaración y la definición coinciden y ambas usan decltype(sizeof(T)) se compila con éxito, y puedo static_assert que el tipo de devolución es size_t . La siguiente compila con éxito:

#include <type_traits> template <typename T> struct Cls { static decltype(sizeof(T)) f(); }; template <typename T> decltype(sizeof(T)) Cls<T>::f() { return 0; } static_assert(std::is_same<std::size_t, decltype(Cls<int>::f())>{}, "");

Actualiza con otro ejemplo. Esto no es un tipo dependiente, pero aún falla.

template <int I> struct Cls { static int f(); }; template <int I> decltype(I) Cls<I>::f() { return I; }

Si uso decltype(I) tanto en la definición como en la declaración, si utilizo int en la definición y declaración funciona, pero las dos difieren.

Actualización 2: Un ejemplo similar. Si se cambia Cls para que no sea una plantilla de clase, se compila correctamente.

template <typename> struct Cls { static int f(); using Integer = decltype(Cls::f()); }; template <typename T> typename Cls<T>::Integer Cls<T>::f() { return I; }

Actualización 3: Otro ejemplo de falla de MM Una función de miembro con plantilla de una clase sin plantilla.

struct S { template <int N> int f(); }; template <int N> decltype(N) S::f() {}

¿Por qué es ilegal que la declaración y la definición estén en desacuerdo solo con un tipo dependiente? ¿Por qué se ve afectado incluso cuando el tipo en sí mismo no es dependiente como con la template <int I> anterior?