4 votes

convertir une fonction membre en pointeur vers une fonction membre

Clang, GCC, MSVC ont des avis différents sur la conversion des fonctions membres. Qui a raison ?

https://gcc.godbolt.org/z/QNsgwd

template<typename T>
struct a
{
    template <typename... Args>
    void va(Args...) {}

    template <typename X>
    void x(X) {}

    void y(int) {}
};

struct b : a<b>
{
    void testva()
    {
        using F = void (a<b>::*)();

        F f = (F)&a<b>::va<int>; // gcc: error, msvc: error, clang: ok
    }

    void testx()
    {
        using F = void (a<b>::*)();

        F f = (F)&a<b>::x<int>;// gcc: error, msvc: ok, clang: ok
    }

    void testy()
    {
        using F = void (a<b>::*)();

        F f = (F)& a<b>::y; // gcc: ok, msvc: ok, clang: ok
    }
};

2 votes

Incluez les erreurs du compilateur dans votre question.

0 votes

Impossible de convertir les fonctions. Voulez-vous dire le journal complet de gcc.godbolt ?

0 votes

Il n'est pas nécessaire que le journal soit complet, mais il faut inclure le message d'erreur (et les lignes d'information suivantes) pour les erreurs de compilation.

3voto

aschepler Points 23731

testx y testy sont bien formées, donc gcc a tort au sujet de testx . Mais la norme est quelque peu vague sur testva .

En commençant par le plus facile, dans testy l'expression &a<b>::y nomme une fonction non modèle qui n'est pas surchargée, elle a donc le type void (a<b>::*)(int) sans qu'il soit nécessaire de procéder à une analyse plus approfondie. La conversion de n'importe quelle fonction pointeur-membre en n'importe quelle autre fonction pointeur-membre est une fonction bien formée reinterpret_cast avec des résultats non spécifiés, sauf s'il est reconverti en type original, et un cast de style C peut faire ce qu'un cast de style C peut faire. reinterpret_cast peut faire.

Pour les fonctions modèles, nous avons [over.over]/1-2 :

L'utilisation d'un nom de fonction surchargée sans argument est résolue dans certains contextes en une fonction, un pointeur de fonction ou un pointeur de fonction membre pour une fonction spécifique de l'ensemble surchargé. Un nom de modèle de fonction est considéré comme le nom d'un ensemble de fonctions surchargées dans de tels contextes. Une fonction de type F est sélectionné pour le type de fonction FT du type cible requis dans le contexte si F (après avoir éventuellement appliqué la conversion de pointeur de fonction) est identique à FT . La cible peut être

  • ...

  • une conversion de type explicite ([expr.type.conv], [expr.static.cast], [expr.cast]),

  • ...

Si le nom est un modèle de fonction, la déduction d'argument de modèle est effectuée ([temp.deduct.funcaddr]), et si la déduction d'argument réussit, la liste d'argument de modèle résultante est utilisée pour générer une seule spécialisation de modèle de fonction, qui est ajoutée à l'ensemble des fonctions surchargées considérées. [ Nota: Comme décrit dans [temp.arg.explicit], si la déduction échoue et que le nom du modèle de fonction est suivi d'une liste d'arguments de modèle explicite, la fonction modèle-id est ensuite examiné pour voir s'il identifie une spécialisation de modèle de fonction unique. Si c'est le cas, le modèle-id est considéré comme une lvalue pour cette spécialisation de modèle de fonction. Le type cible n'est pas utilisé dans cette détermination. - note de fin ]

Cela signifie donc que nous essayons d'abord la déduction d'arguments de modèle pour a<b>::x<int> en le faisant correspondre au type de cible void (a<b>::*)() . Mais il n'y a aucune spécialisation qui puisse donner une correspondance exacte, puisqu'elles ont toutes un argument, et non zéro, donc la déduction échoue. Mais selon la note, il y a aussi [temp.arg.explicit] (paragraphe 3 dans C++17, 4 dans le dernier projet C++20 ) :

Les arguments de modèle de fin qui peuvent être déduits ou obtenus à partir des arguments de modèle par défaut peuvent être omis de la liste des arguments de modèle explicites. Un paquet de paramètres de modèle de queue ([temp.variadic]) qui n'est pas déduit autrement sera déduit comme une séquence vide d'arguments de modèle. ... Dans les contextes où la déduction est effectuée et échoue, ou dans les contextes où la déduction n'est pas effectuée, si une liste d'arguments de modèle est spécifiée et qu'elle identifie, avec tous les arguments de modèle par défaut, une seule spécialisation de modèle de fonction, alors la fonction modèle-id est une lvalue pour la spécialisation du modèle de fonction.

En testx le modèle-id a<b>::x<int> identifie une seule spécialisation de modèle de fonction. Il nomme donc cette spécialisation, et à nouveau le cast de style C est valide avec un résultat non spécifié.

Ainsi, en testva , fait a<b>::va<int> identifier une seule spécialisation ? Il serait certainement possible d'utiliser cette expression pour nommer différentes spécialisations, par le biais de [temp.arg.explicit]/9 :

La déduction d'arguments de modèle peut étendre la séquence d'arguments de modèle correspondant à un paquet de paramètres de modèle, même lorsque la séquence contient des arguments de modèle explicitement spécifiés.

Sauf que ça dit "déduction d'argument de modèle". Et ici, la déduction d'argument de modèle impliquée échoue, car elle a exigé une correspondance impossible avec le type cible void (a<b>::*)() . Donc rien n'explique vraiment si a<b>::va<int> identifie une seule spécialisation, puisqu'aucune autre méthode pour obtenir des arguments de modèle supplémentaires n'est décrite, ou identifie des spécialisations multiples, puisqu'il pourrait être valablement utilisé dans d'autres contextes avec des types cibles correspondants.

0 votes

@YSC Oups, bien vu. Il est possible qu'il y ait d'autres formulations disant template <class X, class Y> void f(X, Y); int g(void (*)(int, int)); int a = g(f<int>); devrait fonctionner ?

0 votes

Je suis perplexe. Si votre interprétation est correcte, est-ce que gcc.godbolt.org/z/YNczD2 sont-elles valables ?

0 votes

@YSC Cependant, je suppose que la note n'aurait pas de sens si [over.over]/1-2 ne s'appliquait pas également à un ID du modèle . Je suis indécis sur le va et cet exemple pose essentiellement les mêmes questions. Il est cependant intéressant de constater que gcc et msvc l'autorisent désormais.

0voto

YSC Points 3386

Clang a raison

[expr.reinterpret.cast]/10

Une valeur prvalue de type "pointeur vers un membre de X de type T1 "peut être explicitement converti en une valeur de pr d'un type différent "pointeur vers un membre de Y de type T2 " si T1 y T2 sont tous deux des types de fonctions ou des types d'objets. La valeur du pointeur de membre nul est convertie en la valeur du pointeur de membre nul du type de destination. Le résultat de cette conversion n'est pas spécifié, sauf dans les cas suivants :

  • La conversion d'une valeur prval de type "pointeur vers une fonction membre" en un autre type de pointeur vers une fonction membre, puis en son type d'origine, donne la valeur originale du pointeur vers une fonction membre.
  • Conversion d'une valeur de type "pointeur vers un membre de l'organisation". X de type T1 "au type "pointeur vers une donnée membre de Y de type T2 "(lorsque les exigences d'alignement de T2 ne sont pas plus strictes que celles de T1 ) et en revenant à son type d'origine, on obtient la valeur originale du pointeur sur le membre.

&a<b>::va<int> et al. son prvalue de type "pointeur vers un membre de a<b> de type void(int) " et le convertir (sans appeler le pointeur de fonction résultant dont la valeur n'est pas spécifiée) est légal.

1 votes

Cette clause décrit reinterpret_cast . Je ne vois aucune reinterpret_cast dans la question.

0 votes

@Sam puisqu'aucun autre casting n'est valide, (F) par défaut, il s'agit d'une réinterprétation.

0 votes

"puisqu'aucun autre casting n'est valable" IMO Une réponse complète commencerait par prouver ceci. C'est peut-être là que réside la divergence

0voto

Artyer Points 3473

Simplifions avec cet exemple :

struct a {
    template <typename... Args>
    void va(Args...) {}

    template <typename X>
    void x(X) {}

    void y(int) {}
};

using no_args = void(a::*)();
using int_arg = void(a::*)(int);

Et essayons les quatre choses suivantes :

reinterpret_cast<no_args>(&MEMBER_FUNCTION);  // (1)
(no_args) &MEMBER_FUNCTION;  // (2)
(no_args) static_cast<int_arg>(&MEMBER_FUNCTION);  // (3)
int_arg temp = &MEMBER_FUNCTION; (no_args) temp;  // (4)

(Remplaçant MEMBER_FUNCTION con &a::va<int> , &a::x<int> y &a::y ).

clang les compile tous.
gcc compile tout sauf (2) avec &a::va<int> y &a::x<int> .
MSVC compile tout sauf (1) et (2) avec &a::va<int> (mais il n'y a pas de problème avec &a::x<int> ).

Remarquez que (3) est essentiellement le même que (4).

https://gcc.godbolt.org/z/a2qqyo montrant un exemple de cela.

Ce que vous pouvez voir de cela est que &MEMBER_FUNCTION n'est pas résolu en un pointeur de fonction membre spécifique dans le cas des modèles, mais s'il est résolu, il est autorisé à le réinterpréter en un autre type de pointeur de fonction membre.

Ce que dit la norme :

[over.over]/1 :

L'utilisation d'un nom de fonction surchargé sans arguments est résolue dans certains contextes en une fonction, un pointeur de fonction ou un pointeur de fonction membre pour une fonction spécifique de l'ensemble de surcharge. Un nom de modèle de fonction est considéré comme le nom d'un ensemble de fonctions surchargées dans de tels contextes. Une fonction de type F est sélectionnée pour le type de fonction FT du type cible requis dans le contexte si F (après avoir éventuellement appliqué la conversion de pointeur de fonction) est identique à FT. [Note : C'est-à-dire que la classe dont la fonction est membre est ignorée lors de la correspondance d'un type de fonction pointeur à membre. -fin de la note ] La cible peut être :
[...]
- une conversion de type explicite ([expr.type.conv], [expr.static.cast], [expr.cast])

Un exemple est donné plus loin :

int f(double);
int f(int);
void g() {
  (int (*)(int))&f;             // cast expression as selector
}

Et quelques autres citations sur les modèles :

[temp.deduct.funcaddr]/1 :

Les arguments du modèle peuvent être déduits du type spécifié lors de la prise de l'adresse d'une fonction surchargée. Le type de fonction du modèle de fonction et le type spécifié sont utilisés comme types de P et A, et la déduction est effectuée comme décrit dans [temp.deduct.type].

[temp.arg.explicit]/4

[...] si une liste d'arguments de modèle est spécifiée et qu'elle identifie, avec les arguments de modèle par défaut, une seule spécialisation de modèle de fonction, alors le template-id est une lvalue pour la spécialisation de modèle de fonction.

Il semble que MSVC ait raison.

&a::va<int> n'est pas résolu à moins que vous ne l'assigniez/transfériez à un fichier de type void(a::*)(int) . Vous devriez également être en mesure de l'attribuer à void(a::*)(int, char) o void(a::*)(int, double, char) où le Args se déduirait comme suit { int, char } y { int, double, char } respectivement. Cela signifie que (no_args) &a::va<int> devrait échouer, car il existe de nombreux ensembles possibles de Args il pourrait s'agir de (tous commencent par int et clang le résout par excès de zèle), et aucun d'entre eux ne prend de paramètres nuls, donc (no_args) &a::va<int> est un static_cast qui devrait échouer.

Quant à &a::x<int> il n'y a qu'une seule fonction possible, donc elle devrait fonctionner exactement de la même manière que la fonction &a::y (Mais gcc ne l'a toujours pas résolu).

0 votes

Êtes-vous sûr ? Est-ce qu'un modèle variadique avec des paramètres de modèle spécifiés signifie quelque chose au moins pour ces paramètres ? Va<int> ne signifie-t-il pas simplement va avec Args = int et rien de plus ?

0 votes

@NN_ Vous pouvez spécifier certains des arguments du modèle, et le reste sera déduit (c'est pourquoi templated_function<> fonctionnera si vous l'assignez à un pointeur, et aussi que le reste du paquet de paramètres ne peut pas encore être déduit comme étant vide au cas où il serait assigné à un pointeur alors qu'il ne l'est pas). Exemple .

0 votes

Je vois. Maintenant, la question est de savoir pourquoi seul clang permet la conversion d'une fonction template variadique en une fonction sans arguments. godbolt.org/z/Racc05 void test() { using F = void(*)() ; using MF = void(a::*)() ; F f = (F)freefunc<int> ; MF mf = (MF)&a::memfunc<int, double> ; }

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