IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Langage C++ Discussion :

questions sur la compilation d'une classe template


Sujet :

Langage C++

  1. #1
    r0d
    r0d est déconnecté
    Expert éminent

    Homme Profil pro
    tech lead c++ linux
    Inscrit en
    Août 2004
    Messages
    4 262
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Ain (Rhône Alpes)

    Informations professionnelles :
    Activité : tech lead c++ linux

    Informations forums :
    Inscription : Août 2004
    Messages : 4 262
    Points : 6 680
    Points
    6 680
    Billets dans le blog
    2
    Par défaut questions sur la compilation d'une classe template
    Bonjour,

    les templates en c++ ne cessent de me plonger dans des abîmes de perplexité.
    Prenons par exemple le code suivant, séparé en 2 fichiers, le main.cpp et un header foo.h :

    Code cpp : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    /* ----- foo.h ----- */
     
    template <typename T>
    class Foo
    {
    private:
    	Bar m_bar;
    };
     
    /* ----- main.cpp ----- */
    #include "Foo.h"
     
    int main()
    {
    	return 0;
    }

    Je compile avec VS 2017, en Release full opti (/O2).
    Et j'obtiens l'erreur suivante:
    1> error C3646: 'm_bar': unknown override specifier
    1> note: see reference to class template instantiation 'Foo<T>' being compiled
    J'ai mis en gras ce qui me pose problème ici : il ne parviens pas à compiler parce qu'il ne connais pas le type Bar. C'est évident puisque je ne lui "dit" jamais ce qu'est ce type, ni même qu'il existe.
    Mais Foo est une classe template, que je n'utilise nulle part. Il me semblait que si on instanciait jamais une classe template, alors la classe n'était jamais compilée.
    Du moins c'est ainsi que j'avais compris comment fonctionnait la compilation de classes template: à chaque fois qu'on va créer une instance où le type template est différent, le compilateur va créer une nouvelle version de cette classe. Donc si on ne l'instancie pas, elle n'est pas compilée.

    J'ai passé pas mal de temps à chercher des infos là-dessus, mais je ne trouve rien qui explique ça clairement. Auriez-vous des explication ou des références à me proposer ?
    « L'effort par lequel toute chose tend à persévérer dans son être n'est rien de plus que l'essence actuelle de cette chose. »
    Spinoza — Éthique III, Proposition VII

  2. #2
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 612
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 612
    Points : 30 612
    Points
    30 612
    Par défaut
    Salut,

    Je crois que visual studio fait juste de l'abus de langage en disant qu'il a "compilé" quelque chose... Il devrait plutôt indiqué avoir parsé / évalué les expressions de la classe Foo<T>, car aucun code (assembleur ou binaire) n'est normalement généré tant que tu ne dis pas quel sera le type de T

    Mais il n'empêche que le compilateur n'accepte de reconnaître que les identifiants qu'il connait, et que cette reconnaissance a lieu lors de la troisième ou de la quatrième étape de la compilation, alors que la génération du code (assembleur ou binaire) se fait ... trois ou quatre étapes plus loin

    Ici, le compilateur te dis essentiellement qu'il ne comprends pas le terme Bar, qui ne correspond à rien pour lui. Et c'est sommes toutes "tout à fait normal"

    Le même code donne une erreur sans doute plus claire à ce sujet lorsqu'on compile avec Gcc, car on obtient le message
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    g++ main.cpp 
    main.cpp:3:5: error: ‘Bar’ does not name a type; did you mean ‘char’?
         Bar m_bar;
         ^~~
         char
    D'ailleurs une déclaration anticipée de Bar ne fera que te permettre de passe "à l'étape suivante", du fait que tu déclare m_bar comme étant une donnée de type Bar, et non une référence ou un pointeur (dont la taille est fixée par le compilateur), "simplement" parce que le compilateur va essayer d'évaluer "aussi loin qu'il en est capable" la valeur qu'il doit renvoyer sur un sizeof(Foo<T>) et que cette valeur correspond à ... sizeof(Bar)+ autres membres éventuels de type connus + autres membres de type T (à adapter une fois que l'on sait par quoi T est remplacé) + alignement éventuel.

    Là encore, Gcc nous fournit sans doute un message plus clair que Visual studio, car, le code
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    /* j'ai tout mis dans un même fichier, par facilité ;) */
    struct Bar;
    template <typename T>
    struct Foo{
        Bar m_bar;
    };
    int main(){
        return 0;
    }
    provoquera un message d'erreur du genre de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    g++ main.cpp 
    main.cpp:4:9: error: field ‘m_bar’ has incomplete type ‘Bar’
         Bar m_bar;
             ^~~~~
    main.cpp:1:8: note: forward declaration of ‘struct Bar’
     struct Bar;
            ^~~
    Par contre, si ta structure Foo ressemblait à
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    /* toujours une déclaration anticipée */
    struct Bar;
    template <typename T>
    struct Foo{
        Bar & m_bar; // remarque l'esperluette: c'est une référence
    };
    Comme le compilateur sait que Bar est un type, e que nous utilisons une référence (qui sera traduite, dans le code binaire, sous la forme d'une adresse mémoire), et que le compilateur connaît parfaitement la taille que prend une adresse mémoire, le compilateur est "parfaitement en mesure" de calculer la "taille de base" de Foo (en attente des éventuelles adaptations dues à la taille spécifique de T, si besoin est), et la compilation ira donc jusqu'à son terme
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  3. #3
    Membre expert
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2011
    Messages
    739
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hérault (Languedoc Roussillon)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Juin 2011
    Messages : 739
    Points : 3 627
    Points
    3 627
    Par défaut
    Il me semblait que si on instanciait jamais une classe template, alors la classe n'était jamais
    le compilateur va essayer d'évaluer "aussi loin qu'il en est capable"
    J'ajoute une petite note sur ce point: le compilateur va vérifier tout ce qui ne dépend pas de template (sauf msvc qui bug la dessus), donc là, il a effectivement besoin d'avoir la déclaration de Bar. Par contre, si la variable membre dépend d'une template, il n'a besoin que d'une déclaration.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    class Bar;
     
    template<class T, class> using lazy = T;
     
    template<class>
    struct Foo
    {
        lazy<Bar, Foo> m_bar; // lazy dépend de Foo qui dépend d'un type template, alors son analyse est retardée.
    };
    Pour msvc, l'analyse est retardée sur l'ensemble du bloc, mais il lui faut quand même les déclarations. La fonction suivante ne compile pas avec clang/gcc, mais compile avec msvc du moment que la fonction n'est pas utilisée.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    template<class T>
    void foo()
    {
      "" + "";
    }

Discussions similaires

  1. Question sur les propriétés d'une classe.
    Par Jean-Jacques Engels dans le forum Langage
    Réponses: 3
    Dernier message: 24/11/2013, 03h51
  2. Question sur la conception d'une classe type Logger
    Par NLS le pingouin dans le forum C++
    Réponses: 14
    Dernier message: 01/11/2010, 13h36
  3. questions sur les "import" et une "class extends"
    Par miniRoshan dans le forum Général Java
    Réponses: 5
    Dernier message: 21/04/2010, 14h35
  4. Réponses: 6
    Dernier message: 31/08/2009, 17h41
  5. Réponses: 2
    Dernier message: 06/10/2008, 13h20

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo