35 votes

Comportement différent de g ++ et clang ++ avec le paramètre de modèle intégral

J'ai le code C ++ 11 suivant.

 #include <type_traits>

using IntType = unsigned long long;

template <IntType N> struct Int {};

template <class T>
struct is_int : std::false_type {};

template <long long N>
struct is_int<Int<N>> : std::true_type {};

int main()
{
    static_assert (is_int<Int<0>>::value, "");
    return 0;
}
 

Clang ++ 3.3 compile le code mais échoue avec l'assertion statique de g ++ 4.8.2

 $ g++ -std=c++11 main.cpp 
main.cpp: In function ‘int main()':
main.cpp:15:5: error: static assertion failed: 
     static_assert (is_int<Int<0>>::value, "");
     ^
$ 
 

Le problème est dû à différents paramètres de modèle intégral. Quel compilateur a raison dans ce cas?

25voto

TemplateRex Points 26447

La surprise

C'est un subtil Clang bug, profondément enfouis dans la Norme. Le problème est que, dans presque tous les cas, la non-type de modèle arguments peuvent être converties dans le type de modèle de paramètre. E. g. l'expression Int<0> a int littérale argument de valeur 0 qui est en cours de conversion vers le type unsigned long long du paramètre de modèle N.

14.8.2 argument de Modèle déduction [temp.déduire]/2 2e balle

-- Non-arguments de type doivent correspondre aux types de la correspondante de non-type de les paramètres de modèle, ou doit être convertible pour les types de la correspondante de non-type de paramètres comme indiqué dans 14.3.2, sinon type de déduction échoue.

Depuis votre modèle de classe is_int<T> a une spécialisation partielle, nous avons besoin de regarder

14.5.5.1 Correspondance de modèle de classe partielle spécialisations [temp.classe.spec.match]

1 Lorsqu'un modèle de classe est utilisé dans un contexte qui nécessite une instanciation de la classe, il est nécessaire de déterminer si la l'instanciation est d'être généré à l'aide de la primaire du modèle ou de l'une de la partielle de spécialisations. Ceci est fait par la correspondance de modèle les arguments de la classe template de la spécialisation avec le modèle des listes d'arguments partiels de spécialisations.

2 Une spécialisation partielle correspond à un réel argument de modèle liste si les arguments de modèle de la spécialisation partielle peut être en déduit le modèle réel de la liste d'arguments (14.8.2).

Il semblerait donc que nous pouvons procéder à la précédente citation de 14.8.2/2 2ème balle et correspondent à la deuxième spécialisation (bien que dans ce cas encore plus complexe de résolution de surcharge jeu devrait être joué).

La résolution

Cependant, il s'avère (comme mentionné par @DyP dans les commentaires) qu'une autre clause de la Norme annule et remplace ceci:

14.8.2.5 en Déduire des arguments de modèle à partir d'un type [temp.déduire.type]

17 Si, dans la déclaration d'un modèle de fonction avec un non-type de modèle à paramètre, la non-type de templateparameter est utilisé dans un expression dans le paramètre de la fonction-liste et, si le correspondant modèle argument est déduit, le modèle-type d'argument doit correspondre à le type de modèle à paramètre exactement, sauf que l' modèle argument déduit à partir d'un tableau lié peut être de n'importe quelle intégrale type. [ Exemple:

template<int i> class A { / ... / };
template<short s> void f(A<s>);
  void k1() {
  A<1> a;
  f(a); // error: deduction fails for conversion from int to short
  f<1>(a); // OK
}

Le résultat est que la spécialisation partielle de l' is_int ne peut pas être déduite car elle ne prend pas exactement du même type (unsigned long long vs long long) que l' Int modèle de classe formelle de non-type de paramètre du modèle.

Vous pouvez résoudre ce problème en donnant la non-type de paramètre de modèle N dans le partiel de la spécialisation de l' is_int du même type que la non-type de paramètre N dans le primaire modèle Int.

template <IntType N>
//        ^^^^^^^^         
struct is_int<Int<N>> : std::true_type {};

Exemple Vivant.

10voto

Nawaz Points 148870

Clang est incohérent. Depuis qu'elle accepte votre code, j'attends le code suivant doit de sortie f(Int<long long>) au lieu de f(T):

using IntType = unsigned long long;
template <IntType N> struct Int {};

template<typename T>
void f(T) { std::cout << "f(T)" << std::endl; }

template<long long N>
void f(Int<N>) { std::cout << "f(Int<long long>)" << std::endl; }

int main()
{
    f(Int<0>{});
}

Mais étonnamment, ce sont les sorties de ce (démo en ligne):

f(T)

Qui montre Int<0> ne correspond PAS à la seconde surcharge qui accepte l'argument de la" Int<N>. Si c'est le cas, alors pourquoi est-il correspondre avec Int<N> lorsqu'il est utilisé comme argument de modèle pour le modèle de classe (dans votre cas)?

Ma conclusion:

  • Si le Bruit est correct dans mon cas, alors il est incorrect dans votre cas.
  • Si le Bruit est correct dans votre cas, alors il est incorrrect dans mon cas.

De toute façon, Clang semble avoir un bug.

GCC, d'autre part, est conforme au moins. Cela ne prouve pas cependant qu'il n'a pas de bug, il pourrait signifier qu'elle a un bug dans les deux cas! À moins que quelqu'un arrive avec la standardese et montrer qu'il a bug trop, je vais faire confiance à GCC dans ce cas.

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