30 votes

Le type ou l'argument dépendant dans decltype dans la définition de fonction ne parvient pas à se compiler lorsqu'il est déclaré sans decltype

J'ai joué avec déduite des types de retour dans les définitions que de résoudre le même type que la déclaration. Ceci fonctionne:

template <typename>
struct Cls {
  static std::size_t f();
};

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

Mais si je change la définition de quelque chose qui devrait être équivalent par le remplacement, sizeof(int) avec sizeof(T) il échoue

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

de gcc erreur (clang est presque identique):

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();
                        ^

Le même problème se pose avec la fonction des types de paramètres:

template <typename>
struct Cls {
  static void f(std::size_t);
};

template <typename T>
void Cls<T>::f(decltype(sizeof(T))) { } // sizeof(int) works instead

Inconnu encore, si la déclaration et la définition de match et à la fois utiliser decltype(sizeof(T)) il compile correctement, et j'ai peut - static_assert que le type de retour est - size_t. Le suivant compile correctement:

#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())>{}, "");

Mise à jour avec un autre exemple. Ce n'est pas un type de charge, mais ne fonctionne toujours pas.

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

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

Si j'utilise decltype(I) à la fois la définition et la déclaration qu'il fonctionne, si j'utilise int tant pour la définition et déclaration de travaux, mais avoir les deux diffèrent échoue.


Mise à jour 2: Un exemple similaire. Si Cls est modifié pour ne plus être un modèle de classe, il compile correctement.

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

template <typename T>
typename Cls<T>::Integer Cls<T>::f() { return I; }

Mise à jour 3: Un autre exemple d'échec de M. M. basé sur Un modèle de fonction de membre d'un non-basé sur un modèle de classe.

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

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

Pourquoi est-il illégal pour la déclaration et la définition d'être en désaccord avec un type dépendant uniquement? Pourquoi est-il affecté, même quand le type lui-même n'est pas dépendant, comme avec l' template <int I> - dessus?

10voto

Yuki Points 1654

Parce que quand il y a un paramètre du modèle impliqué, decltype renvoie un unqiue type de charge selon la norme, voir ci-dessous. Si il n'y a pas de paramètre de modèle puis il se résout à un évident size_t. Dans ce cas, vous avez à choisir, soit la déclaration et la définition est indépendante de l'expression (par exemple, size_t/decltype(sizeof(int))), un type de retour, ou les deux ont dépendante de l'expression (par exemple, decltype(sizeof(T))), qui s'est résolue à un unique type de charge et considéré comme l'équivalent, si leurs expressions sont équivalentes (voir ci-dessous).

Dans ce post, je suis en utilisant le standard C++ projet de N3337.

§ 7.1.6.2 [dcl.type.simpl]

¶ 4

Le type indiqué par decltype(e) est définie comme suit: - si e est un sans parenthèse id-expression ou un sans parenthèse membre de la classe d'accès (5.2.5), decltype(e) est le type de l'entité nommée par courriel. Si il n'y a pas une telle entité, ou si e désigne un ensemble de surcharge func- tions, le programme est mal formé;

- sinon, si e est un xvalue, decltype(e) est en T&&, où T est le type de e;

- sinon, si e est une lvalue, decltype(e) est en T&, où T est le type de e;

- sinon, decltype(e) est le type de l'e.

C'est ce qui explique qu'est - decltype(sizeof(int)). Mais pour l' decltype(sizeof(T)) il y a un autre article expliquant ce que c'est.

§ 14.4 [temp.type]

¶ 2

Si une expression e implique un paramètre du modèle, decltype(e) désigne un unique type de charge. Deux de ces decltype spécificateurs de se référer au même type que si leurs expressions sont équivalentes (14.5.6.1). [ Note: toutefois, il peut être un alias, par exemple, par une définition de type-nom. - la note de fin ]

Dans Clang LLVM sources de la version 3.9 dans le fichier 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 phrase importante qui commence comme "d'Où un decltype...". Nouveau clarifie la situation.

De nouveau dans Clang sources de la version 3.9 dans le fichier 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);
}

Donc, vous voyez Clang réunit et choisit ces types de charge de l' decltype ) à partir d'une série spéciale.

Pourquoi le compilateur est tellement stupide qu'il ne voit pas que l'expression de l' decltype est sizeof(T) toujours size_t? Oui, c'est évident pour un lecteur humain. Mais lors de la conception et de mettre en œuvre une grammaire formelle et sémantique des règles, en particulier pour les complexes tels langages comme C++, vous devez regrouper des problèmes et définir des règles pour eux, plutôt que de simplement venir avec une règle pour chaque problème particulier, dans la façon dont vous simplement ne sera pas en mesure de se déplacer avec votre langue/conception du compilateur. La même ici, il n'y a pas de règle: si decltype a un appel de fonction, expression qui n'a pas besoin de paramètres du modèle de résolution de résoudre decltype pour le type de retour de la fonction. Il n'y a plus que cela, il ya tellement de nombreux cas, vous avez besoin pour couvrir, que vous venez avec un plus générique de la règle, à l'instar de la cité au-dessus de la norme (14.4[2]).

En outre, un même type de non-évidente cas auto, decltype(auto) trouvé par AndyG en C++-14 (N4296, § 7.1.6.4 [dcl.spec.auto], 12/13):

§ 7.1.6.4 [dcl.spec.auto]

¶ 13

Redeclarations ou spécialisations d'une fonction ou d'une fonction de modèle avec une déclaration de type de retour qui utilise un espace réservé type doit aussi utiliser l'espace réservé, pas une déduit type. [ Exemple:

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

Les changements en C++17, Numéro de Document >= N4582

Changement dans le projet de norme N4582 à partir de Mars 2016 (grâce à bogdan) généralise l'énoncé:

§ 17.4 (ancien § 14.4) [temp.type]

¶ 2

Si une expression e est dépendant du type de (17.6.2.2), decltype(e) désigne un unique type de charge. Deux de ces decltype spécificateurs de se référer au même type que si leurs expressions sont équivalentes (17.5.6.1). [ Note: toutefois, ce type peut être un alias, par exemple, par une définition de type-nom. - la note de fin ]

Ce changement conduit à une autre section décrivant le type dépendante de l'expression qui semble étrange à notre cas particulier.

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

¶ 4

Les Expressions de l'un des formulaires suivants sont jamais dépendant du type (parce que le type de l'expression ne peut pas être charge):

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

Il y a d'autres sections sur la valeur dépendante des expressions où l' sizeof peut être une valeur dépendant si l' type-id - dépendants. Il n'y a aucun rapport entre la valeur dépendante de l'expression et de l' decltype. Après réflexion, je n'ai trouvé aucune raison de s' decltype(sizeof(T)) ne doit pas ou ne peut pas résoudre en size_t. Et je suppose que c'était assez sournois changement ("comporte un paramètre de modèle" à "dépendant du type") dans la norme que les développeurs de compilateurs ne paient pas beaucoup d'attention à (peut-être submergé par de nombreux autres changements, peut-être ne pense pas qu'il pourrait réellement changer quelque chose, juste une simple formulation d'amélioration). Le changement ne fait sens, parce qu' sizeof(T) n'est pas dépendant du type, c'est la valeur-dépendante. decltype(e)s'opérande est un non évaluée opérande, c'est à dire ne se soucie pas de valeur, uniquement sur le type. C'est pourquoi, decltype renvoie un type unique uniquement lorsqu' e est dépendant du type. sizeof(e) seulement de la valeur-dépendante.

J'ai essayé le code avec clang 5, gcc 8 -std=c++1z - le même résultat: erreur. Je suis allé plus loin et a essayé ce code:

template <typename>
struct Cls {
  static std::size_t f();
};

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

La même erreur a été donné, de même qu' sizeof(sizeof(T)) n'est ni de type ou de la valeur-dépendante (voir ce post). Cela me donne une raison de supposer que les compilateurs de travail dans une ancienne façon de C++-11/14 standard (c'est à dire "comporte un paramètre de modèle") comme dans la source de l'extrait ci-dessus de clang 3,9 source (je peux vérifier que le dernier développement de clang 5.0 a les mêmes lignes, je n'ai rien trouvé sur ce nouveau changement dans la norme), mais pas en fonction du type d'.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X