5 votes

L'instanciation est-elle effectuée pour le type qui participe à l'ordonnancement partiel ?

Récemment, j'ai constaté que GCC a modifié le comportement lors de la commande partielle, le cas concret est le suivant :

#include <iostream>
template<class T>
struct unknow_context{
    using type = int;
};
template<class U>
void show(typename unknow_context<U>::type, U){ // candidate #1
    std::cout<<"#1\n";
}

template<class T>
void show(int, T){   // candidate #2
    std::cout<<"#2\n";
}
int main(){
    show(0,0);  
}

Le résultat est le suivant, Clang empreintes #2 (toute version de Clang a imprimé un résultat cohérent). Cependant, GCC a un comportement différent. L'ancienne version de CCG empreintes #2 Au lieu de cela, la dernière CCG se plaint de l'ambiguïté des fonctions des candidats. Voyons ce que dit la norme à propos de l'ordre partiel.
temp.deduct.partial#2

Le processus de déduction utilise le type transformé comme modèle d'argument et le type original de l'autre modèle comme modèle de paramètre. Ce processus est effectué deux fois pour chaque type impliqué dans la comparaison de l'ordre partiel : une fois en utilisant le modèle-1 transformé comme modèle argument et le modèle-2 comme modèle paramètre et une autre fois en utilisant le modèle-2 transformé comme modèle argument et le modèle-1 comme modèle paramètre.

Nous pouvons donc obtenir deux séries de paires P/A pour le candidat #1 et #2 respectivement. L'un d'entre eux est le #1 comme A, l'original #2 comme P. L'autre est l'original #1 comme P, la valeur transformée #2 comme A. Les deux ensembles seront donc donnés comme suit :

#a  
|--------|------------------------------------------|
| P (#2) | A (#1)                                   |   
|--------|------------------------------------------|  
| int    |  typename unknow_context<UniqueA>::type  |
|--------|------------------------------------------|   
| T      | UniqueB                                  |   
|--------|------------------------------------------|  

T peut être déduit de UniqueB . Pour la première série, la règle dit :

Si un P particulier ne contient aucun paramètre de modèle qui participe à la déduction des arguments de modèle, ce P n'est pas utilisé pour déterminer l'ordre.

Ainsi, nous les mettons d'abord de côté et les examinons plus tard .

#b  
|----------------------------------|-------|
|P (#1)                            |A (#2) |   
|----------------------------------|-------|  
| typename unknow_context<U>::type |int    |  
|----------------------------------|-------|
| U                                |UniqueA|  
|----------------------------------|-------|

Pour un contexte non éduqué, sa valeur peut être obtenue ailleurs.

Dans certains contextes, cependant, la valeur ne participe pas à la déduction de type, mais utilise les valeurs des arguments du modèle qui ont été soit déduites ailleurs, soit spécifiées explicitement. Si un paramètre de modèle n'est utilisé que dans des contextes non déduits et n'est pas explicitement spécifié, la déduction des arguments de modèle échoue.

Nous pouvons donc ignorer la paire typename unknow_context<U>::type / int et de considérer U / UniqueA . U peut être déduit de UniqueA .

Si la déduction réussit pour un type donné, le type du modèle d'argument est considéré comme étant au moins aussi spécialisé que le type du modèle de paramètre.

De #b on obtient le résultat suivant #2 est au moins aussi spécialisé que #1 .

La question se pose dans la #a set . La deuxième paire ne pose pas de problème, on peut dire que la deuxième partie de #1 est au moins aussi spécialisée que la deuxième partie de la #2 .

Toutefois, la règle permettant de déterminer si un modèle de fonction F est plus spécialisé qu'un modèle de fonction G est définie comme suit :

Le modèle de fonction F est au moins aussi spécialisé que le modèle de fonction G si, pour pour chaque paire de types utilisés pour déterminer l'ordre, le type de F est au moins aussi spécialisé que le type de G. F est plus spécialisé que G si F est au moins aussi spécialisé que G et si G n'est pas au moins aussi spécialisé que F. .

Nous veillons donc à former des paires int / typename unknow_context<UniqueA>::type Bien que la règle précise que "P n'est pas utilisé pour déterminer l'ordre". Cependant, une note importante de la norme précise que :

[Note : Sous [temp.deduct.call] et [temp.deduct.partial], si P ne contient aucun paramètre de modèle apparaissant dans des contextes déduits, aucune déduction n'est effectuée, donc P et A n'ont pas nécessairement la même forme . -note de fin]

Ainsi, dès aujourd'hui, à partir de la P/A set #a , #1 est toujours au moins aussi spécialisé que #2 (succès de la déduction). Je pense donc que la dernière GCC devrait être correcte (ambiguë, aucune des deux n'est plus spécialisée que l'autre).

Question 1 :

Quel est le bon compilateur ?

Question 2 :

l'instanciation de la spécialisation doit-elle être effectuée lors de la commande partielle ? La norme ne semble pas préciser si l'instanciation sera effectuée.

#include <iostream>
template<class T>
struct unknow_context{
    using type = T;
};
template<class U>
void show(typename unknow_context<U>::type, U){
    std::cout<<"#1\n";
}

template<class T>
void show(T, T){
    std::cout<<"#2\n";
}
int main(){
    show(0,0);
}

Tous ont choisi #2 . Je suis préoccupé par les P/A pair c'est à dire :

|----|------------------------------------------------------------|
|P   |A                                                           |    
|----|------------------------------------------------------------|  
|T   |typename unknow_context<UniqueA>::type /*Is it equivalent to| 
|    | UniqueA? */                                                |  
|----|------------------------------------------------------------|   
|T   |UniqueA                                                     |  
|----|------------------------------------------------------------|  

si typename unknow_context<UniqueA>::type sera calculée pour UniqueA ? Il semble que le compilateur traite typename unknow_context<UniqueA>::type en tant que type unique plutôt que de le calculer en UniqueA .

2voto

dfri Points 11222

<em>(Comme cette réponse est en accord avec l'OP et en désaccord avec les implémentations de Clang et de GCC (trunk), elle peut être quelque peu incomplète, mais elle met au moins en évidence certains problèmes existants avec les règles d'ordre partiel, en particulier pour l'ordre partiel lorsque des contextes non éduqués sont impliqués). </em>)


Question 1 : quel est le bon compilateur ?

Notons tout d'abord qu'à partir de GCC 11(/trunk), les deux compilateurs sont d'accord avec leur interprétation, et choisissent le candidat #2 comme étant plus spécialisé que le candidat #1, et comme le précise [over.match.best]/1.7 la résolution des surcharges choisit la première comme la meilleure fonction viable.

Cependant, votre argument de [temp.func.order] semble valable, en particulier l'accent mis sur les [temp.deduct.partial]/4 :

[...] Si un P ne contient aucun paramètre de modèle qui participe à la déduction des arguments de modèle, que P n'est pas utilisé pour déterminer l'ordre.

ce qui signifie que

[...] pour chaque paire de types utilisés pour déterminer l'ordre [...]

en [temp.deduct.partial]/10 ne doit pas prendre en compte les (P, A) paire (int, typename unknow_context<UniqueA>::type) pour l'ordre, et pour la paire restante, le candidat n° 1 est au moins aussi spécialisé que le candidat n° 2, ce qui signifie que le candidat n° 1 est au moins aussi spécifié que le candidat n° 2 selon [temp.deduct.partial]/10.

Ainsi, je pense que Clang a tort, et que GCC a encore une fois tort selon GCC 11(/trunk), mais comme je le souligne ci-dessous, les règles d'ordre partiel dans les cas où des contextes non éduqués sont impliqués ont été, historiquement, sous-spécifiées ( 02-0051/N1393 a abordé nombre d'entre eux), et aujourd'hui, ils sont pour le moins vagues (peut-être encore sous-spécifiés), car nous constatons une grande variance dans leur mise en œuvre.


Question 2 La spécialisation : l'instanciation de la spécialisation doit-elle être effectuée lors de la commande partielle ?

Je ne suis pas certain de la section la plus pertinente ; elle pourrait sans doute relever de la section suivante [temp.inst]/9 ,

Une mise en œuvre ne doit pas instancier implicitement [...], à moins qu'une telle instanciation ne soit nécessaire.

et la note non normative de [temp.deduct]/8 :

[Note : La substitution en types et expressions peut avoir des effets tels que l'instanciation de spécialisations de modèles de classes et/ou de spécialisations de modèles de fonctions, [...] fin de la note ]

mais oui , raisonnablement instanciation de la spécialisation de unknown_context serait requis dans le cadre de l'argument modèle substitution des arguments déduits dans le cadre de l'ordonnancement partiel. Nous pouvons utiliser l'astuce de l'ami injecté pour forcer une violation diagnostiquée de l'ODR si cela est vrai pour le compilateur, et GCC et Clang sont d'accord, rejetant le programme suivant :

// Due to the injected friend, the identity class may 
// only be instantiated once within a given TU, or 
// the program, diagnosable ([basic.odr.def]/1). 
template<class T>
struct identity {
    using type = T;
    friend void f() {}
};
void f();

template<class U>
void show(typename identity<U>::type, U) {}

template<class T>
void show(T, T) {}

int main(){
    identity<char> i;  // f() now defined
    f();               // OK
    show(0,0);         // error: redefines f() as part of 
                       //        substitution in partial ordering.
}

avec l'erreur d'instruction :

error: redefinition of 'f'
       friend void f() {}
            ^
note: in instantiation of template class 
      'identity<int>' requested here
       void show(typename identity<U>::type, U) {}

note: while substituting deduced template arguments 
into function template 'show' [with U = int]

Les incertitudes historiques dans l'ordonnancement partiel et les contextes non éduqués

Nous pouvons commencer par l'actif/ouvert Numéro 455 du CWG :

455. Ordonnancement partiel et arguments non déduits

La manière dont la surcharge et l'ordonnancement partiel gèrent les paires d'arguments correspondants non déduits n'est pas claire.

[...]

John Spicer : Il peut y avoir (ou non) un problème concernant le traitement correct des contextes non éduqués dans les règles de classement partiel.

et notent que les compilateurs, notamment Clang et GCC, ne sont pas toujours d'accord sur la manière d'appliquer les règles d'ordre partiel dans les cas où des contextes non éduqués sont impliqués.

Jason Merrill du GCC a écrit Numéro 1337 du CWG qui a été marqué comme étant un duplicata du numéro 455 du CWG. Jason est actif dans un certain nombre de rapports de bogues ouverts de GCC, en particulier sur les points suivants

  • Bug 86193 - Ordonnancement partiel des paramètres de modèles non typiques avec des types dépendants

que [ l'accent mine]

Jason Merrill 2018-06-18 19:09:13 UTC

que, de la même manière, G++ (et EDG) rejette, et que clang accepte. Je pense que G++ a raison ici : [...]

Cela semble être un domaine non spécifié dans la norme.

ainsi qu'à

  • Bug 91133 - [8/9/10/11 Régression] Erreur "la spécialisation partielle n'est pas plus spécialisée que".

que

Il s'agit en fait d'une question d'ordre partiel ; [...]

Ce point a été rejeté comme étant ambigu par le GCC depuis au moins la version 4.1. Il est également rejeté par EDG/icc. Il est accepté par clang et msvc, comme le cas de test original.

Le problème concerne la déduction de l'ordre partiel de #1 à partir de #2 : nous déduisons int pour U à partir du deuxième argument, et Id::type pour U à partir du troisième argument, et ceux-ci ne concordent pas, donc la déduction pour le troisième argument échoue dans les deux sens, et les fonctions sont ambiguës.

Ce problème est lié aux questions ouvertes 455 et 1337.

Je ne sais pas quel raisonnement clang/msvc utilise pour conclure que le numéro 2 est plus spécialisé.

Ainsi, comme le montrent les citations ci-dessus, leurs interprétations historiquement différentes de la norme (éventuellement sous-spécifiée) semblent avoir été intentionnelles, bien qu'une telle variance de mise en œuvre soit typiquement un signe d'imprécision dans le segment correspondant de la norme, au mieux (sous-spécifié, au pire).

Voir aussi CCG Rapport de bogue 67228 .


L'erreur d'ambiguïté du GCC n'était-elle qu'une régression ?

L'ancienne version de GCC imprime #2, au lieu de cela, la dernière version de GCC se plaint que les fonctions candidates sont ambiguës.

Comme indiqué plus haut, le comportement du GCC pour le programme suivant

#include <iostream>
template<class T>
struct unknow_context{
    using type = int;
};
template<class U>
void show(ypename unknow_context<U>::type, U){ // candidate #1
    std::cout<<"#1\n";
}

template<class T>
void show(int, T){   // candidate #2
    std::cout<<"#2\n";
}
int main(){
    show(0,0);  
}

est le suivant :

  • jusqu'à GCC 7.3.0 inclus : impressions #2 (identique à clang)
  • GCC 8/9/10 : erreur : appel ambigu
  • GCC trunk/11 : retour à l'impression #2

Je n'ai pas trouvé de rapport de bogue pour une régression [8/9/10] de ceci, mais il semble que GCC utilise à nouveau la même interprétation que Clang (accepter le programme et trouver #2 comme plus spécialisé), ce qui semble être une erreur pour nous deux (le candidat #1 devrait être considéré comme au moins aussi spécialisé que le candidat #2).

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