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

C++ Discussion :

CMake: manière élégante de gérer extern template + librairie partagée


Sujet :

C++

  1. #1
    Nouveau membre du Club
    Homme Profil pro
    Ingénieur
    Inscrit en
    Septembre 2015
    Messages
    36
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Ingénieur
    Secteur : Industrie

    Informations forums :
    Inscription : Septembre 2015
    Messages : 36
    Points : 27
    Points
    27
    Par défaut CMake: manière élégante de gérer extern template + librairie partagée
    Bonjour

    Ma question est quasiment en tout point identique à celle ci sur stackoverflow qui n'a pas trouvé de réponse: https://stackoverflow.com/questions/...-shared-object

    Je reproduis le code pour vous simplifier la vie:

    CMakeLists.txt:

    Code CMakeLists : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    cmake_minimum_required(VERSION 3.10)
    project(export_templates CXX)
    include(GenerateExportHeader)
     
    set(CMAKE_INCLUDE_CURRENT_DIR ON)
    set(CMAKE_CXX_VISIBILITY_PRESET hidden)
     
    add_library(mylib SHARED header.hpp mylib.cpp)
    generate_export_header(mylib)

    header.hpp
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    #pragma once
    #include <vector>
    template<typename T>
    struct foo
    {
        std::size_t bar() {
            std::vector<T> t;
            t.resize(10);
            return t.size();
        }
    };
     
     
    extern template struct foo<int>;
    mylib.cpp
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    #include "header.hpp"
     
    #include <mylib_export.h>
    template struct MYLIB_EXPORT foo<int>;
    - Visual Studio générera une librairie partagée avec les symboles appropriés exportés.
    - Les compilateurs GNU réussiront à compiler, avec un warning "warning: type attributes ignored after type is already defined [-Wattributes]" concernant template struct MYLIB_EXPORT foo<int>, puis si on link la librairie à un executable on aura des références indéfinies.

    La solution pour les compilateurs GNU est de mettre MYLIB_EXPORT dans le extern template de header.hpp et non pas dans mylib.cpp (mais si on fait ça, c'est Visual Studio qui va échouer à compiler la librairie).
    Enfin on peut mettre la macro dans les 2 fichiers, dans ce cas je pense que ça compile et link comme il faut, mais avec un tas de warnings bien dégueux.

    Le problème, c'est que je ne trouve pas de manière élégante de le faire à la manière d'un generate_export_header. Par exemple il serait assez élégant d'avoir 2 macros automatiquement générées par CMake du type MYLIB_EXTERN_TEMPLATE (vide avec MSVC, égale à MYLIB_EXPORT pour les GNU) et MYLIB_TEMPLATE_INSTANTIATE (vide pour les GNU, égale à MYLIB_EXPORT pour MSVC).
    Bien sur je pourrais le faire à la main dans un header qui inclut mylib_export.h, puis utiliser ce header à la place de mylib_export.h dans tous les fichiers qui l'utilisaient, mais se reposer sur CMake qui connait vachement mieux toutes les subtilités des compilateurs serait plus élégant à mon sens.

    Quelqu'un aurait il une idée?

  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,

    Voici à quoi ressemblerait un CMakeLists.txt "classique"
    Code CMakeLists : 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
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    cmake_minimum_required(VERSION 3.14)
    project(mycoollib VERSION 1.0.0.0 LANGUAGES CXX)
    # le standard C++ utilisé
    set(CMAKE_CXX_STANDARD 17)
    set(CMAKE_CXX_EXTENSIONS OFF)
    set(CMAKE_CXX_STANDARD_REQUIRED ON)
     
    #les dossier d'installation
    include(GNUInstallDirs)
     
    set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR})
    set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR})
    set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/${CMAKE_INSTALL_BINDIR})
     
    # Offer the user the choice of overriding the installation directories
    set(INSTALL_LIBDIR ${CMAKE_INSTALL_LIBDIR} CACHE PATH "Installation directory for libraries")
    set(INSTALL_BINDIR ${CMAKE_INSTALL_BINDIR} CACHE PATH "Installation directory for executables")
    set(INSTALL_INCLUDEDIR ${CMAKE_INSTALL_INCLUDEDIR} CACHE PATH "Installation directory for header files")
    if(WIN32 AND NOT CYGWIN)
      set(DEF_INSTALL_CMAKEDIR CMake)
    else()
      set(DEF_INSTALL_CMAKEDIR share/cmake/${PROJECT_NAME})
    endif()
    set(INSTALL_CMAKEDIR ${DEF_INSTALL_CMAKEDIR} CACHE PATH "Installation directory for CMake files")
     
    # pour que les différents dossiers soient utilisés comme chemin d'inclusion
     
    set(CMAKE_INCLUDE_CURRENT_DIR ON)
    # pour la génération du fichier d'en-tête
     
    include(GenerateExportHeader)
     
    # par facilité, le nom de la cible sera celui du projet)
    set(TGT_NAME ${PROJECT_NAME})
     
    add_library(${TGT_NAME} SHARED)
    target_sources(${TGT_NAME}
        PRIVATE         #les fichiers d'implémentation
            test.cpp
        PUBLIC  # les fichiers d'en-tête publics
               test.hpp
    )
    # NOTA: j'utilise souvent les listes SRCS et HEADERS pour les projets plus ambitieux
    set_target_properties(${TGT_NAME}
        PROPERTIES
            POSITION_INDEPENDENT_CODE 1        # utilise le flag -fPIC si  besoin
            OUTPUT_NAME "${TGT_NAME}"          # utilise le nom du projet comme nom de bibliothèque
            DEBUG_POSTFIX "-d"                 # la version debug s'appellera ${TGT_NAME}-d
            SOVERSION ${PROJECT_VERSION_MAJOR} # sous linux, la compatibilité est garantie pour toutes les versions
                                               # majeures identiques
    )
    #parce que  je veux un export bien particulier
    string(TOUPPER ${TGT_NAME} NAME_UP)
    generate_export_header(${TGT_NAME}
    	BASE_NAME ${NAME_UP}
    	EXPORT_FILE_NAME ${TGT_NAME}_config.h
    	)
    #une application pour montrer que ca marche?
    add_executable(MyApp
        main.cpp)
    target_link_libraries(MyApp ${TGT_NAME})
    J'ai fait en sorte de le laisser "bien complet", avec quelques commentaires supplémentaires, histoire que tu puisse te faire une idée de ce à quoi il devrait ressembler, mais, ce qui importe pour la création d'une bibliothèque partagée tient dans les lignes 52 à 57.
    Ce qui importe, ce sont surtout
    • la ligne 55 ( BASE_NAME ${NAME_UP}) qui forcera le nom des symbole en majuscules (autrement CMake générerait le symbole mycoollib_EXPORT)
    • la ligne 56 (EXPORT_FILE_NAME ${TGT_NAME}_config.h) qui défini le nom du fichier d'en-tête qui sera généré (car je ne veux pas que CMake le choisisse pour moi )
    Le fichier d'en-tête de la bibliothèque, basé sur ton propre code ressemblera à
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    #pragma once
    // pour disposer, entre autres, du symbole MYCOOLLIB_EXPORT
    #include <MyCoolLib_config.h>
    #include <vector>
    template <typename T>
    struct MYCOOLLIB_EXPORT Foo{
        size_t bar();
        std::vector<T> tab;
    };
    NOTA: tu remarqueras que je n'ai pas fourni l'implémentation de la fonction bar.

    Je l'ai fait exprès, car, autrement, il serait impossible de savoir si, en utilisant un type différent que ceux pour lesquels nous avons recours à l'instanciation explicite, si cela fonctionne parce que le compilateur a généré le code pour le type indiqué (au niveau de l'application) ou si c'est parce que l'éditeur de liens n'a pas réagi

    Bien sur, j'aurai aussi fourni un fichier d'implémentation (test.cpp) qui ressemble à
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    #include <test.hpp>
     
        template <typename T>
        size_t Foo<T>::bar(){
            tab.resize(10);
            return tab.size();
        }
    /* instanciation explicite de la classe
     * uniquement pour le type int
     */
    template struct MYCOOLLIB_EXPORT Foo<int>;
    Et, pour finir, voici un fichier d'implémentation (main.cpp) permettant de générer une application utilisant la bibliothèque générée. Il est, lui aussi, tout à fait basique et prend la forme de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    #include <test.hpp>
    #include <iostream>
    int main(){
        Foo<int> i;
        std::cout<<i.bar()<<"\n";
    }
    NOTA: Si on le modifiait pour utiliser (par exemple) Foo<float> à la place de Foo<int>, nous aurions bel et bien une erreur à l'édition de liens, car la code correspondant pour la spécialisaiton avec un float n'exite pas dans la bibliothèque.

    Tout cela fonctionne sans aucun problème sous debian (sid); malheureusement, je n'ai aucune version de visua studio à ma disposition pour l'instant
    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
    Nouveau membre du Club
    Homme Profil pro
    Ingénieur
    Inscrit en
    Septembre 2015
    Messages
    36
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Ingénieur
    Secteur : Industrie

    Informations forums :
    Inscription : Septembre 2015
    Messages : 36
    Points : 27
    Points
    27
    Par défaut
    Merci de ta réponse Koala01, mais je crains que tu aies raté un mot clé important dans mon post: c'est extern template
    D'ailleurs le but n'est pas d'interdire les autres instanciations à l'utilisateur, dans ton code c'est le cas (on ne peut utiliser que Foo<int> :s).

    (au passage, je suis presque certain que le MYCOOLLIB_EXPORT après template <typename T> struct n'a aucune utilité).

    Je vais plutôt fournir un triplet header, "header d'implémentation" et le cpp pour une classe de la librairie (fcl) que je cherche à rendre compatible avec MinGW et MSVC sous Windows en cas de librairie partagée (ça fonctionne en statique):

    AABB.h
    (ici FCL_EXPORT est totalement inutile, déjà testé avec gcc/clang sur Linux, et msvc/mingw sur Windows, et AppleClang sur MacOS. Avec MinGW, les dllimport font échouer la compilation lors de l'utilisation de la librairie)

    Code : 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
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    #ifndef FCL_BV_AABB_H
    #define FCL_BV_AABB_H
     
    #include "fcl/common/types.h"
     
    namespace fcl
    {
     
    /// @brief A class describing the AABB collision structure, which is a box in 3D
    /// space determined by two diagonal points
    template <typename S_>
    class FCL_EXPORT AABB
    {
    public:
     
      using S = S_;
     
      /// @brief The min point in the AABB
      Vector3<S> min_;
     
      /// @brief The max point in the AABB
      Vector3<S> max_;
     
      /// @brief Creating an AABB with zero size (low bound +inf, upper bound -inf)
      AABB();
     
      /// @brief Creating an AABB at position v with zero size
      AABB(const Vector3<S>& v);
     
      /// @brief Creating an AABB with two endpoints a and b
      AABB(const Vector3<S>& a, const Vector3<S>&b);
     
      /// @brief Creating an AABB centered as core and is of half-dimension delta
      AABB(const AABB<S>& core, const Vector3<S>& delta);
     
      /// @brief Creating an AABB contains three points
      AABB(const Vector3<S>& a, const Vector3<S>& b, const Vector3<S>& c);
     
      /// @brief Check whether two AABB are overlap
      bool overlap(const AABB<S>& other) const;
     
      /// @brief Check whether the AABB contains another AABB
      bool contain(const AABB<S>& other) const;
     
      /// @brief Check whether two AABB are overlapped along specific axis
      bool axisOverlap(const AABB<S>& other, int axis_id) const;
     
      /// @brief Check whether two AABB are overlap and return the overlap part
      bool overlap(const AABB<S>& other, AABB<S>& overlap_part) const;
     
      /// @brief Check whether the AABB contains a point
      bool contain(const Vector3<S>& p) const;
     
      /// @brief Merge the AABB and a point
      AABB<S>& operator += (const Vector3<S>& p);
     
      /// @brief Merge the AABB and another AABB
      AABB<S>& operator += (const AABB<S>& other);
     
      /// @brief Return the merged AABB of current AABB and the other one
      AABB<S> operator + (const AABB<S>& other) const;
     
      /// @brief Width of the AABB
      S width() const;
     
      /// @brief Height of the AABB
      S height() const;
     
      /// @brief Depth of the AABB
      S depth() const;
     
      /// @brief Volume of the AABB
      S volume() const;
     
      /// @brief Size of the AABB (used in BV_Splitter to order two AABBs)
      S size() const;
     
      /// @brief Radius of the AABB
      S radius() const;
     
      /// @brief Center of the AABB
      Vector3<S> center() const;
     
      /// @brief Distance between two AABBs; P and Q, should not be nullptr, return
      /// the nearest points
      S distance(const AABB<S>& other, Vector3<S>* P, Vector3<S>* Q) const;
     
      /// @brief Distance between two AABBs
      S distance(const AABB<S>& other) const;
     
      /// @brief whether two AABB are equal
      bool equal(const AABB<S>& other) const;
     
      /// @brief expand the half size of the AABB by delta, and keep the center
      /// unchanged.
      AABB<S>& expand(const Vector3<S>& delta);
     
      /// @brief expand the aabb by increase the thickness of the plate by a ratio
      AABB<S>& expand(const AABB<S>& core, S ratio);
    };
     
    using AABBf = AABB<float>;
    using AABBd = AABB<double>;
     
    /// @brief translate the center of AABB by t
    template <typename S, typename Derived>
    AABB<S> translate(
        const AABB<S>& aabb, const Eigen::MatrixBase<Derived>& t);
     
    } // namespace fcl
     
    #include "fcl/math/bv/AABB-inl.h"
     
    #endif
    AABB-inl.h
    (ici c'est le extern template (explicit instantiation declaration) qu'il est important de voir. Le fait qu'il y ait FCL_EXPORT fait que ça fonctionne bien avec gcc et clang sur Linux, MacOS, MinGW, mais n'est pas valide avec msvc (warning et le dllexport est probablement ignoré))

    Code : 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
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
    271
    272
    273
    274
    275
    276
    277
    278
    279
    280
    281
    282
    283
    284
    285
    286
    287
    288
    289
    290
    291
    292
    293
    294
    295
    296
    297
    298
    299
    300
    301
    302
    303
    304
    305
    306
    307
    308
    309
    310
    311
    312
    313
    314
    315
    316
    317
    318
    319
    #ifndef FCL_BV_AABB_INL_H
    #define FCL_BV_AABB_INL_H
     
    #include "fcl/math/bv/AABB.h"
     
    namespace fcl
    {
     
    //==============================================================================
    extern template
    class FCL_EXPORT AABB<double>;
     
    //==============================================================================
    template <typename S>
    AABB<S>::AABB()
      : min_(Vector3<S>::Constant(std::numeric_limits<S>::max())),
        max_(Vector3<S>::Constant(-std::numeric_limits<S>::max()))
    {
      // Do nothing
    }
     
    //==============================================================================
    template <typename S>
    AABB<S>::AABB(const Vector3<S>& v) : min_(v), max_(v)
    {
      // Do nothing
    }
     
    //==============================================================================
    template <typename S>
    AABB<S>::AABB(const Vector3<S>& a, const Vector3<S>& b)
      : min_(a.cwiseMin(b)),
        max_(a.cwiseMax(b))
    {
      // Do nothing
    }
     
    //==============================================================================
    template <typename S>
    AABB<S>::AABB(const AABB<S>& core, const Vector3<S>& delta)
      : min_(core.min_ - delta),
        max_(core.max_ + delta)
    {
      // Do nothing
    }
     
    //==============================================================================
    template <typename S>
    AABB<S>::AABB(
        const Vector3<S>& a,
        const Vector3<S>& b,
        const Vector3<S>& c)
      : min_(a.cwiseMin(b).cwiseMin(c)),
        max_(a.cwiseMax(b).cwiseMax(c))
    {
      // Do nothing
    }
     
    //==============================================================================
    template <typename S>
    bool AABB<S>::overlap(const AABB<S>& other) const
    {
      if ((min_.array() > other.max_.array()).any())
        return false;
     
      if ((max_.array() < other.min_.array()).any())
        return false;
     
      return true;
    }
     
    //==============================================================================
    template <typename S>
    bool AABB<S>::contain(const AABB<S>& other) const
    {
      if ((min_.array() > other.min_.array()).any())
        return false;
     
      if ((max_.array() < other.max_.array()).any())
        return false;
     
      return true;
    }
     
    //==============================================================================
    template <typename S>
    bool AABB<S>::axisOverlap(const AABB<S>& other, int axis_id) const
    {
      if(min_[axis_id] > other.max_[axis_id]) return false;
     
      if(max_[axis_id] < other.min_[axis_id]) return false;
     
      return true;
    }
     
    //==============================================================================
    template <typename S>
    bool AABB<S>::overlap(const AABB<S>& other, AABB<S>& overlap_part) const
    {
      if(!overlap(other))
      {
        return false;
      }
     
      overlap_part.min_ = min_.cwiseMax(other.min_);
      overlap_part.max_ = max_.cwiseMin(other.max_);
      return true;
    }
     
    //==============================================================================
    template <typename S>
    bool AABB<S>::contain(const Vector3<S>& p) const
    {
      if ((min_.array() > p.array()).any())
        return false;
     
      if ((max_.array() < p.array()).any())
        return false;
     
      return true;
    }
     
    //==============================================================================
    template <typename S>
    AABB<S>& AABB<S>::operator +=(const Vector3<S>& p)
    {
      min_ = min_.cwiseMin(p);
      max_ = max_.cwiseMax(p);
      return *this;
    }
     
    //==============================================================================
    template <typename S>
    AABB<S>& AABB<S>::operator +=(const AABB<S>& other)
    {
      min_ = min_.cwiseMin(other.min_);
      max_ = max_.cwiseMax(other.max_);
      return *this;
    }
     
    //==============================================================================
    template <typename S>
    AABB<S> AABB<S>::operator +(const AABB<S>& other) const
    {
      AABB res(*this);
      return res += other;
    }
     
    //==============================================================================
    template <typename S>
    S AABB<S>::width() const
    {
      return max_[0] - min_[0];
    }
     
    //==============================================================================
    template <typename S>
    S AABB<S>::height() const
    {
      return max_[1] - min_[1];
    }
     
    //==============================================================================
    template <typename S>
    S AABB<S>::depth() const
    {
      return max_[2] - min_[2];
    }
     
    //==============================================================================
    template <typename S>
    S AABB<S>::volume() const
    {
      return width() * height() * depth();
    }
     
    //==============================================================================
    template <typename S>
    S AABB<S>::size() const
    {
      return (max_ - min_).squaredNorm();
    }
     
    //==============================================================================
    template <typename S>
    S AABB<S>::radius() const
    {
      return (max_ - min_).norm() / 2;
    }
     
    //==============================================================================
    template <typename S>
    Vector3<S> AABB<S>::center() const
    {
      return (min_ + max_) * 0.5;
    }
     
    //==============================================================================
    template <typename S>
    S AABB<S>::distance(const AABB<S>& other, Vector3<S>* P, Vector3<S>* Q) const
    {
      S result = 0;
      for(std::size_t i = 0; i < 3; ++i)
      {
        const S& amin = min_[i];
        const S& amax = max_[i];
        const S& bmin = other.min_[i];
        const S& bmax = other.max_[i];
     
        if(amin > bmax)
        {
          S delta = bmax - amin;
          result += delta * delta;
          if(P && Q)
          {
            (*P)[i] = amin;
            (*Q)[i] = bmax;
          }
        }
        else if(bmin > amax)
        {
          S delta = amax - bmin;
          result += delta * delta;
          if(P && Q)
          {
            (*P)[i] = amax;
            (*Q)[i] = bmin;
          }
        }
        else
        {
          if(P && Q)
          {
            if(bmin >= amin)
            {
              S t = 0.5 * (amax + bmin);
              (*P)[i] = t;
              (*Q)[i] = t;
            }
            else
            {
              S t = 0.5 * (amin + bmax);
              (*P)[i] = t;
              (*Q)[i] = t;
            }
          }
        }
      }
     
      return std::sqrt(result);
    }
     
    //==============================================================================
    template <typename S>
    S AABB<S>::distance(const AABB<S>& other) const
    {
      S result = 0;
      for(std::size_t i = 0; i < 3; ++i)
      {
        const S& amin = min_[i];
        const S& amax = max_[i];
        const S& bmin = other.min_[i];
        const S& bmax = other.max_[i];
     
        if(amin > bmax)
        {
          S delta = bmax - amin;
          result += delta * delta;
        }
        else if(bmin > amax)
        {
          S delta = amax - bmin;
          result += delta * delta;
        }
      }
     
      return std::sqrt(result);
    }
     
    //==============================================================================
    template <typename S>
    bool AABB<S>::equal(const AABB<S>& other) const
    {
      return min_.isApprox(other.min_, std::numeric_limits<S>::epsilon() * 100)
          && max_.isApprox(other.max_, std::numeric_limits<S>::epsilon() * 100);
    }
     
    //==============================================================================
    template <typename S>
    AABB<S>& AABB<S>::expand(const Vector3<S>& delta)
    {
      min_ -= delta;
      max_ += delta;
      return *this;
    }
     
    //==============================================================================
    template <typename S>
    AABB<S>& AABB<S>::expand(const AABB<S>& core, S ratio)
    {
      min_ = min_ * ratio - core.min_;
      max_ = max_ * ratio - core.max_;
      return *this;
    }
     
    //==============================================================================
    template <typename S, typename Derived>
    AABB<S> translate(
        const AABB<S>& aabb, const Eigen::MatrixBase<Derived>& t)
    {
      AABB<S> res(aabb);
      res.min_ += t;
      res.max_ += t;
      return res;
    }
     
    } // namespace fcl
     
    #endif
    AABB.cpp
    (là on a la définition explicite de l'instanciation. Il ne faut pas mettre FCL_EXPORT pour gcc/clang sous Linux, Mac et MinGW, mais ce n'est pas dramatique (juste pas propre), ce sera juste ignoré avec un tas de warning a priori. Par contre pour msvc c'est ici qu'il attend le dllexport)

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    #include "fcl/math/bv/AABB-inl.h"
     
    namespace fcl
    {
     
    template
    class AABB<double>;
     
    } // namespace fcl
    Sinon ce post (autre projet) résume en une phrase mon pb: https://reviews.llvm.org/D61118

  4. #4
    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
    FCL_EXPORT est, je suppose, une macro qui s'adapte en fonction du compilateur, sans passer par une quelconque manipulation de cmake. Pourquoi vouloir quelque chose de différent pour la version extern template ?

  5. #5
    Nouveau membre du Club
    Homme Profil pro
    Ingénieur
    Inscrit en
    Septembre 2015
    Messages
    36
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Ingénieur
    Secteur : Industrie

    Informations forums :
    Inscription : Septembre 2015
    Messages : 36
    Points : 27
    Points
    27
    Par défaut
    FCL_EXPORT est la macro classique qui se trouve dans le fichier généré par generate_export_header dans CMake.

    Pourquoi vouloir mettre quelque chose de différent devant extern template?
    Car Visual Studio ne fonctionne pas de la même manière que les compilateurs GNU.
    A la compilation de la librairie:
    - GNU: attend __attribute__ ((visibility ("default"))) au niveau de la déclaration de l'instanciation du template.
    - Visual Studio: attend __declspec(dllexport) au niveau de la définition de l'instanciation du template.
    A l'utilisation de la librairie:
    - GNU et Visual Studio: on ne doit pas avoir de mot clé (pas de __declspec(dllimport) par exemple) au niveau de la déclaration de l'instanciation du template.

    Ainsi si je ne veux utiliser que FCL_EXPORT, je dois la mettre à la fois devant la définition et la déclaration.
    Et donc:
    - les compilateurs GNU font cracher un tas de warning liés à __attribute__ ((visibility ("default"))) devant la définition.
    - Visual Studio va cracher un tas de warning liés à __declspec(dllexport) combiné à extern (devant la déclaration). Il va également se plaindre lors du link de la librairie partagée dans l'executable (warning C4251 à cause des __declspec(dllimport) devant la déclaration qui n'ont rien à faire là), même si ça passe.

    Bref c'est pas propre.

  6. #6
    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
    Ah mais j'ai bien compris la raison de vouloir une autre macro, la seconde partie de ma phrase était en référence à la première: je ne savais pas que la macro FCL_EXPORT était généré par cmake. Du coup, la raison de passer par cmake à tout son sens. Mais à part regarder comment est fait la fonction pour en faire une similaire, je ne vois pas . Le mieux serait de remonter le problème aux développeurs de cmake ou GenerateExportHeader.

  7. #7
    Rédacteur/Modérateur


    Homme Profil pro
    Network game programmer
    Inscrit en
    Juin 2010
    Messages
    7 115
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : Canada

    Informations professionnelles :
    Activité : Network game programmer

    Informations forums :
    Inscription : Juin 2010
    Messages : 7 115
    Points : 32 965
    Points
    32 965
    Billets dans le blog
    4
    Par défaut
    Le template doit avoir tout son code pour être utilisé.
    Donc si tu veux définir un type particulier... un simple typedef/using est suffisant.
    Vu que le code entier est nécessaire, les parties templates sont header only.

    La SFML est utilisable en DLL et possède de tels trucs, vector2i par exemple.
    https://github.com/SFML/SFML/blob/ma...ctor2.hpp#L253
    Pensez à consulter la FAQ ou les cours et tutoriels de la section C++.
    Un peu de programmation réseau ?
    Aucune aide via MP ne sera dispensée. Merci d'utiliser les forums prévus à cet effet.

  8. #8
    Nouveau membre du Club
    Homme Profil pro
    Ingénieur
    Inscrit en
    Septembre 2015
    Messages
    36
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Ingénieur
    Secteur : Industrie

    Informations forums :
    Inscription : Septembre 2015
    Messages : 36
    Points : 27
    Points
    27
    Par défaut
    SFML ne propose pas d'instanciations de template pré-compilés, ces parties sont header only, il n'y a donc aucun symbole de vector2i dans les dll ou lib. Le typedef/using n'est que du sucre syntaxique.

    Je répète que l'important dans mon post est: extern template. L'enlever n'est pas une option, tout le but lorsque cela a été introduit dans la librairie FCL, était d'accélérer la compilation lors de l'utilisation de la librairie avec les types classiques, tout en permettant des types plus exotiques.

    Je cite cppreference:
    An explicit instantiation declaration (an extern template) skips implicit instantiation step: the code that would otherwise cause an implicit instantiation instead uses the explicit instantiation definition provided elsewhere (resulting in link errors if no such instantiation exists). This can be used to reduce compilation times by explicitly declaring a template instantiation in all but one of the source files using it, and explicitly defining it in the remaining file.

  9. #9
    Nouveau membre du Club
    Homme Profil pro
    Ingénieur
    Inscrit en
    Septembre 2015
    Messages
    36
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Ingénieur
    Secteur : Industrie

    Informations forums :
    Inscription : Septembre 2015
    Messages : 36
    Points : 27
    Points
    27
    Par défaut
    Je recentre ma question (je pense que jo_link_noir l'a bien compris):

    Comment injecter de manière élégante (idéalement dans le export.h généré par generate_export_header, car sinon ça casse la logique des includes de la librairie, et pour la maintenance ça va pas le faire) des macros de preprocessing qui seraient:

    Si MSVC:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    #define FCL_EXPLICIT_INSTANTIATION_DECLARATION FCL_EXPORT
    #define FCL_EXPLICIT_INSTANTIATION_DEFINITION
    Sinon:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    #define FCL_EXPLICIT_INSTANTIATION_DECLARATION
    #define FCL_EXPLICIT_INSTANTIATION_DEFINITION FCL_EXPORT
    FCL_EXPLICIT_INSTANTIATION_DECLARATION serait utilisé dans les déclarations explicites (dans les fichiers *inl.h).
    FCL_EXPLICIT_INSTANTIATION_DEFINITION serait utilisé dans les définitions explicites (dans les fichiers *.cpp).


    Par exemple, generate_export_header crée ceci avec Visual Studio et lib partagée:
    Code : 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
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
     
    #ifndef FCL_EXPORT_H
    #define FCL_EXPORT_H
     
    #ifdef FCL_STATIC_DEFINE
    #  define FCL_EXPORT
    #  define FCL_NO_EXPORT
    #else
    #  ifndef FCL_EXPORT
    #    ifdef fcl_EXPORTS
            /* We are building this library */
    #      define FCL_EXPORT __declspec(dllexport)
    #    else
            /* We are using this library */
    #      define FCL_EXPORT __declspec(dllimport)
    #    endif
    #  endif
     
    #  ifndef FCL_NO_EXPORT
    #    define FCL_NO_EXPORT 
    #  endif
    #endif
     
    #ifndef FCL_DEPRECATED
    #  define FCL_DEPRECATED __declspec(deprecated)
    #endif
     
    #ifndef FCL_DEPRECATED_EXPORT
    #  define FCL_DEPRECATED_EXPORT FCL_EXPORT FCL_DEPRECATED
    #endif
     
    #ifndef FCL_DEPRECATED_NO_EXPORT
    #  define FCL_DEPRECATED_NO_EXPORT FCL_NO_EXPORT FCL_DEPRECATED
    #endif
     
    #if 0 /* DEFINE_NO_DEPRECATED */
    #  ifndef FCL_NO_DEPRECATED
    #    define FCL_NO_DEPRECATED
    #  endif
    #endif
     
    #endif /* FCL_EXPORT_H */
    Je voudrais ceci:
    Code : 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
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
     
    #ifndef FCL_EXPORT_H
    #define FCL_EXPORT_H
     
    #ifdef FCL_STATIC_DEFINE
    #  define FCL_EXPORT
    #  define FCL_NO_EXPORT
    #else
    #  ifndef FCL_EXPORT
    #    ifdef fcl_EXPORTS
            /* We are building this library */
    #      define FCL_EXPORT __declspec(dllexport)
    #    else
            /* We are using this library */
    #      define FCL_EXPORT __declspec(dllimport)
    #    endif
    #  endif
     
    #  ifndef FCL_NO_EXPORT
    #    define FCL_NO_EXPORT 
    #  endif
    #endif
     
    #ifndef FCL_DEPRECATED
    #  define FCL_DEPRECATED __declspec(deprecated)
    #endif
     
    #ifndef FCL_DEPRECATED_EXPORT
    #  define FCL_DEPRECATED_EXPORT FCL_EXPORT FCL_DEPRECATED
    #endif
     
    #ifndef FCL_DEPRECATED_NO_EXPORT
    #  define FCL_DEPRECATED_NO_EXPORT FCL_NO_EXPORT FCL_DEPRECATED
    #endif
     
    #if 0 /* DEFINE_NO_DEPRECATED */
    #  ifndef FCL_NO_DEPRECATED
    #    define FCL_NO_DEPRECATED
    #  endif
    #endif
     
    #define FCL_EXPLICIT_INSTANTIATION_DECLARATION FCL_EXPORT
    #define FCL_EXPLICIT_INSTANTIATION_DEFINITION
     
    #endif /* FCL_EXPORT_H */

  10. #10
    Nouveau membre du Club
    Homme Profil pro
    Ingénieur
    Inscrit en
    Septembre 2015
    Messages
    36
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Ingénieur
    Secteur : Industrie

    Informations forums :
    Inscription : Septembre 2015
    Messages : 36
    Points : 27
    Points
    27
    Par défaut
    Je viens de réaliser quelque chose:
    Pour Visual Studio le __declspec(dllimport) au niveau des déclarations explicites lors de l'utilisation de la librairie est bien valide (par contre le dllexport doit bien se trouver au niveau de la définition). J'ai des warnings C4251 sur certaines déclarations explicites car FCL utilise des membres publics de type Eigen3 ou certains conteneurs STL, sans les exporter ou les instancier explicitement (ou sans utiliser PImpl).

  11. #11
    Membre éprouvé
    Femme Profil pro
    ..
    Inscrit en
    Décembre 2019
    Messages
    562
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Âge : 94
    Localisation : Autre

    Informations professionnelles :
    Activité : ..

    Informations forums :
    Inscription : Décembre 2019
    Messages : 562
    Points : 1 253
    Points
    1 253
    Par défaut
    Bonjour,

    Quelque chose dans cet ordre d'idées peut-être ?

    header.hpp
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
     
     
    ....
     
    #ifdef FCL_STATIC_DEFINE
      extern template struct foo<int>;
    #else
    #  ifdef FCL_EXPORT
    #    ifndef fcl_EXPORTS
             extern template struct FCL_EXPORT foo<int>;
    #    endif
    #  endif
    #endif
    mylib.cpp
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
     
     
    ....
     
    #ifdef FCL_STATIC_DEFINE
      template struct foo<int>;
    #else
    #  ifdef FCL_EXPORT
    #    ifdef fcl_EXPORTS
            template struct FCL_EXPORT foo<int>;
    #    endif
    #  endif
    #endif

  12. #12
    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
    Citation Envoyé par Tony310 Voir le message
    Merci de ta réponse Koala01, mais je crains que tu aies raté un mot clé important dans mon post: c'est extern template
    Le fait est que extern sert juste à dire au compilateur ne sert que pour dire au comiplateur quelque chose du genre de
    Ne t'en fais pas, j'utilise <telle notion> dont je vais parler plus loin, je peux t'assurer que tu la trouvera dans le code binaire (sans doute déclarée dans une autre unité de compilation
    De son coté, le but de la programmation générique (les template) reviennent à dire au compilateur quelque chose du genre de
    Je ne sais pas encore quel type de donnée je vais manipuler, mais voici comment je vais les manipuler, il faudra générer le code binaire exécutable adapté au type de donnée manipulé en cas de besoin
    Si tu places l'implémentation de tes fonctions (et de tes fonctions membres de classe) template dans un fichier séparé, qui n'est pas inclus dans ton fichier d'en-tête (par exemple, en fournissant l'instanciation dans un fichier *.cpp qui contient l’instanciation explicite pour les types qui t'intéressent), tu n'as absolument aucune raison de regrouper les deux termes.

    Car, voici ce que dit cppreference à ce sujet:
    An explicit instantiation declaration (an extern template) skips implicit instantiation step: the code that would otherwise cause an implicit instantiation instead uses the explicit instantiation definition provided elsewhere (resulting in link errors if no such instantiation exists). This can be used to reduce compilation times by explicitly declaring a template instantiation in all but one of the source files using it, and explicitly defining it in the remaining file.
    Voici un problème auquel nous pourrions être confrontés.
    fichier d'en-tête (*.hpp)
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    template <typename T>
    struct MaClasse{
        /* le contenu de la classe,
         */
    }:
    /* l'implémentation des fonctions vient ici */
    /* Si je fais une définition explcite ici, elle aura lieu pour
     * TOUTES LES UNITES DE COMPILATION incluant
     * -- de manière directe ou indirecte -- le fichier d'en-tête
     *
     * cela résulterait  d'office en une erreur à l'édition de liens
     * pour cause de "définitions multiples
     */
    template class MaClasse<int>; // BOUM
    La solution à ce problème passe par le fait de déclarer l'instanciation explicite comme externe. Le même fichier d'en-tête "corrigé" prendrait donc la forme de
    fichier d'en-tête (*.hpp)
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    template <typename T>
    struct MaClasse{
        /* le contenu de la classe,
         */
    }:
    /* l'implémentation des fonctions vient ici */
    extern template class MaClasse<int>; // OK: indique au compilateur qu'il ne doit pas générer le 
                                         // code exécutable à partir de cette instanciation explicite
    Mais, du coup, il faut qu'une unité de compilation dispose -- effectivement -- de l'instanciation explicite, autrement, ce ne sera plus une erreur de "définition multiple" que nous obtiendrons de la part de l'éditeur de lien, mais une erreur de "symbole indéfini" . Il faudra donc avoir un fichier d'implémentation proche de
    fichier d'implémentation (*.cpp)
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    template class MaClasse<int>; // Instanciation explicite qui servira effectivement à la génération du code
                                  // binaire exécutable
    Seulement, cela n'aura effectivement du sens qu'à condition que
    1. ton fichier d'en-tête contienne effectivement "quelque chose" qui incite le compilateur à fournir l'instanciation explicite (comme un alias de type, par exemple) et
    2. si l'implémentation des fonctions se retrouve effectivement dans le fichier d'en-tête, et que, du coup, le compilateur voudra générer le code binaire exécutable correspondant danschaque unité de compilation incluant ce fichier d'en-tête.



    D'ailleurs le but n'est pas d'interdire les autres instanciations à l'utilisateur, dans ton code c'est le cas (on ne peut utiliser que Foo<int> :s).
    C'est parce que mon but était de te fournir un code minimum compilable pour l'exemple, il te revient, à toi seul, de choisir les instanciations explicites qui te conviennent effectivement

    J'aurais tout aussi bien pu en faire une pour les quinze types primitifs qui existent ou décider de n'en faire qu'une pour deux (ou les trois ) types réels.

    Et, pour le reste, de quels autres types veux tu parler

    Car, ta classe AABBB représente une notion bien particulière: la notion de collision entre deux éléments. Cette notion n'a de sens -- soyons simple -- que si les données manipulées sont des valeur numérique (entières ou réelle). Et encore, parce que les types qui t'intéressent réellement, ce sont ceux que le reste de ta bibliothèque utilise "au jour le jour".

    Si, parmi les quinze type primitifs qui correspondent à cette définition de "valeur numérique (entière ou réelle)" que nous propose le langage, tu décide de ne supporter que les float et les double dans le reste de ton projet, tu t'en fous royalement si ta notion de collision ne supporte pas les int ou les unsigned long long (*)

    Il n'y a -- par exemple -- aucun sens à tester la collision entre deux tableaux de ... chaînes de caractères, nous sommes bien d'accord

    Mais, peut-être, fais tu référence à des types de données spécifiques, comme au GLuint proposé par OpenGL

    Sur ce point, je peux te rassurer, car ce ne sont que des alias sur l'un des types primitifs "classiques" (et encore, ce sont des symboles préprocesseur, dans le cas de OpenGL). Or, ce n'est pas le nom de l'alias de type qui est pris en compte, mais bien le type réel auquel l'alias correspond

    (*) Depuis maintenant plus de quinze ans que je m'intéresse au C++, si j'exclus la bibliothèque standard, qui est effectivement adaptée à n'importe quel type, y compris les types définis par l'utilisateur, je n'ai pas croisé un seul projet pour lequel il était effectivement indispensable de pouvoir prendre n'importe quel type de données en compte.

    Il y a toujours des prérequis que le type utilisé comme paramètre template doit respecter pour pouvoir être utilisé dans le cadre du besoin que l'on décide de remplir, et, parmi tous les types qui satisfont à ces prérequis, il est particulièrement rare que l'on décide d'en accepter l'ensemble
    (au passage, je suis presque certain que le MYCOOLLIB_EXPORT après template <typename T> struct n'a aucune utilité).
    Heuu, de quel MYCOOLLIB_EXPORT parles tu

    Si c'est de celui dans le fichier d'en-tête, il est absolument obligatoire pour générer la dll sous windows.

    Si c'est de celui que l'on retrouve au niveau de l'instanciation explicite, tu as tout à fait raison, il n'est pas obligatoire (mais il ne mange a priori pas de pain ).
    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

  13. #13
    Nouveau membre du Club
    Homme Profil pro
    Ingénieur
    Inscrit en
    Septembre 2015
    Messages
    36
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Ingénieur
    Secteur : Industrie

    Informations forums :
    Inscription : Septembre 2015
    Messages : 36
    Points : 27
    Points
    27
    Par défaut
    Citation Envoyé par koala01 Voir le message
    Le fait est que extern sert juste à dire au compilateur ne sert que pour dire au comiplateur quelque chose du genre de


    De son coté, le but de la programmation générique (les template) reviennent à dire au compilateur quelque chose du genre de

    Si tu places l'implémentation de tes fonctions (et de tes fonctions membres de classe) template dans un fichier séparé, qui n'est pas inclus dans ton fichier d'en-tête (par exemple, en fournissant l'instanciation dans un fichier *.cpp qui contient l’instanciation explicite pour les types qui t'intéressent), tu n'as absolument aucune raison de regrouper les deux termes.

    Car, voici ce que dit cppreference à ce sujet:

    Voici un problème auquel nous pourrions être confrontés.
    fichier d'en-tête (*.hpp)
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    template <typename T>
    struct MaClasse{
        /* le contenu de la classe,
         */
    }:
    /* l'implémentation des fonctions vient ici */
    /* Si je fais une définition explcite ici, elle aura lieu pour
     * TOUTES LES UNITES DE COMPILATION incluant
     * -- de manière directe ou indirecte -- le fichier d'en-tête
     *
     * cela résulterait  d'office en une erreur à l'édition de liens
     * pour cause de "définitions multiples
     */
    template class MaClasse<int>; // BOUM
    La solution à ce problème passe par le fait de déclarer l'instanciation explicite comme externe. Le même fichier d'en-tête "corrigé" prendrait donc la forme de
    fichier d'en-tête (*.hpp)
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    template <typename T>
    struct MaClasse{
        /* le contenu de la classe,
         */
    }:
    /* l'implémentation des fonctions vient ici */
    extern template class MaClasse<int>; // OK: indique au compilateur qu'il ne doit pas générer le 
                                         // code exécutable à partir de cette instanciation explicite
    Mais, du coup, il faut qu'une unité de compilation dispose -- effectivement -- de l'instanciation explicite, autrement, ce ne sera plus une erreur de "définition multiple" que nous obtiendrons de la part de l'éditeur de lien, mais une erreur de "symbole indéfini" . Il faudra donc avoir un fichier d'implémentation proche de
    fichier d'implémentation (*.cpp)
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    template class MaClasse<int>; // Instanciation explicite qui servira effectivement à la génération du code
                                  // binaire exécutable
    Seulement, cela n'aura effectivement du sens qu'à condition que
    1. ton fichier d'en-tête contienne effectivement "quelque chose" qui incite le compilateur à fournir l'instanciation explicite (comme un alias de type, par exemple) et
    2. si l'implémentation des fonctions se retrouve effectivement dans le fichier d'en-tête, et que, du coup, le compilateur voudra générer le code binaire exécutable correspondant danschaque unité de compilation incluant ce fichier d'en-tête.
    Héhé, toute la démarche dans ton message amène tout simplement au code que je montre dans mon post initial et dans mon 2è post



    Citation Envoyé par koala01
    C'est parce que mon but était de te fournir un code minimum compilable pour l'exemple, il te revient, à toi seul, de choisir les instanciations explicites qui te conviennent effectivement

    J'aurais tout aussi bien pu en faire une pour les quinze types primitifs qui existent ou décider de n'en faire qu'une pour deux (ou les trois ) types réels.

    Et, pour le reste, de quels autres types veux tu parler

    Car, ta classe AABBB représente une notion bien particulière: la notion de collision entre deux éléments. Cette notion n'a de sens -- soyons simple -- que si les données manipulées sont des valeur numérique (entières ou réelle). Et encore, parce que les types qui t'intéressent réellement, ce sont ceux que le reste de ta bibliothèque utilise "au jour le jour".

    Si, parmi les quinze type primitifs qui correspondent à cette définition de "valeur numérique (entière ou réelle)" que nous propose le langage, tu décide de ne supporter que les float et les double dans le reste de ton projet, tu t'en fous royalement si ta notion de collision ne supporte pas les int ou les unsigned long long (*)

    Il n'y a -- par exemple -- aucun sens à tester la collision entre deux tableaux de ... chaînes de caractères, nous sommes bien d'accord

    Mais, peut-être, fais tu référence à des types de données spécifiques, comme au GLuint proposé par OpenGL

    Sur ce point, je peux te rassurer, car ce ne sont que des alias sur l'un des types primitifs "classiques" (et encore, ce sont des symboles préprocesseur, dans le cas de OpenGL). Or, ce n'est pas le nom de l'alias de type qui est pris en compte, mais bien le type réel auquel l'alias correspond

    (*) Depuis maintenant plus de quinze ans que je m'intéresse au C++, si j'exclus la bibliothèque standard, qui est effectivement adaptée à n'importe quel type, y compris les types définis par l'utilisateur, je n'ai pas croisé un seul projet pour lequel il était effectivement indispensable de pouvoir prendre n'importe quel type de données en compte.

    Il y a toujours des prérequis que le type utilisé comme paramètre template doit respecter pour pouvoir être utilisé dans le cadre du besoin que l'on décide de remplir, et, parmi tous les types qui satisfont à ces prérequis, il est particulièrement rare que l'on décide d'en accepter l'ensemble
    Je ne suis pas le mainteneur de la librairie pour laquelle je cherche à corriger le bug. Ta remarque est intéressante, mais dans ce cas de peu d'utilité car cela casserait l'API existante.
    Sinon fmt le fait je crois bien. Et on peut d'ailleurs bien y voir une gestion des macros bien spécifique au niveau des définitions et déclarations explicites (dans le sens où il utilise bien 2 macros bien spécifiques pour ça, en plus de la macro classique pour exporter des symboles de manière portable). Sauf que fmt n'utilise pas generate_export_header, il gère tout ça "à la main" donc cela ne m'aide pas trop pour ma question originelle bien précise.

    Citation Envoyé par koala01
    Heuu, de quel MYCOOLLIB_EXPORT parles tu

    Si c'est de celui dans le fichier d'en-tête, il est absolument obligatoire pour générer la dll sous windows.

    Si c'est de celui que l'on retrouve au niveau de l'instanciation explicite, tu as tout à fait raison, il n'est pas obligatoire (mais il ne mange a priori pas de pain ).
    Mmmhh, je vais revérifier. Pour ton exemple peut être. Pour le mien, j'ai quelques doutes, mais je ferai des tests pour voir tout ça

  14. #14
    Nouveau membre du Club
    Homme Profil pro
    Ingénieur
    Inscrit en
    Septembre 2015
    Messages
    36
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Ingénieur
    Secteur : Industrie

    Informations forums :
    Inscription : Septembre 2015
    Messages : 36
    Points : 27
    Points
    27
    Par défaut
    Citation Envoyé par kaitlyn Voir le message
    Bonjour,

    Quelque chose dans cet ordre d'idées peut-être ?

    header.hpp
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
     
     
    ....
     
    #ifdef FCL_STATIC_DEFINE
      extern template struct foo<int>;
    #else
    #  ifdef FCL_EXPORT
    #    ifndef fcl_EXPORTS
             extern template struct FCL_EXPORT foo<int>;
    #    endif
    #  endif
    #endif
    mylib.cpp
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
     
     
    ....
     
    #ifdef FCL_STATIC_DEFINE
      template struct foo<int>;
    #else
    #  ifdef FCL_EXPORT
    #    ifdef fcl_EXPORTS
            template struct FCL_EXPORT foo<int>;
    #    endif
    #  endif
    #endif
    Ceci va générer un tas de warnings quelque soit le compilateur. Visual Studio ne va pas être content de voir dllexport couplé à extern. Les compilateurs gnu ne vont pas aimer l'autre. Dans les 2 cas ça va fonctionner, mais avec pleins de warnings.
    De plus il y a environ 150 définitions et déclarations explicites dans cette librairie, la logique de preprocessing doit se faire en amont.

  15. #15
    Membre éprouvé
    Femme Profil pro
    ..
    Inscrit en
    Décembre 2019
    Messages
    562
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Âge : 94
    Localisation : Autre

    Informations professionnelles :
    Activité : ..

    Informations forums :
    Inscription : Décembre 2019
    Messages : 562
    Points : 1 253
    Points
    1 253
    Par défaut
    Je n'ai pas développé hier, j'avais moins d'une minutes. Cependant, dans l'un des fichiers la condition de compilation est "#if defined" et l'autre est "#ïf not defined" permettant de savoir si on utilise ou si on compile la bibliothèque, le but étant justement d'empêcher qu'un "extern" se retrouve avec un "exportdll", ce qui serait contradictoire.

  16. #16
    Nouveau membre du Club
    Homme Profil pro
    Ingénieur
    Inscrit en
    Septembre 2015
    Messages
    36
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Ingénieur
    Secteur : Industrie

    Informations forums :
    Inscription : Septembre 2015
    Messages : 36
    Points : 27
    Points
    27
    Par défaut
    Oui pardon je n'avais pas vu ça. Mais ça ne marche pas pour les compilateurs GNU, eux attendent __attribute__ ((visibility ("default"))) au niveau de la déclaration explicite, et non pas au niveau de la définition explicite.

  17. #17
    Membre éprouvé
    Femme Profil pro
    ..
    Inscrit en
    Décembre 2019
    Messages
    562
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Âge : 94
    Localisation : Autre

    Informations professionnelles :
    Activité : ..

    Informations forums :
    Inscription : Décembre 2019
    Messages : 562
    Points : 1 253
    Points
    1 253
    Par défaut
    Autre piste alors, donc pour ton message en #9
    Citation Envoyé par Tony310 Voir le message
    ...
    generate_export_header() accepte une option [url=https://cmake.org/cmake/help/latest/module/GenerateExportHeader.html]CUSTOM_CONTENT_FROM_VARIABLE[/ul], la partie variable devant être préalablement définie en fonction du compilateur identifié par cmake.

  18. #18
    Nouveau membre du Club
    Homme Profil pro
    Ingénieur
    Inscrit en
    Septembre 2015
    Messages
    36
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Ingénieur
    Secteur : Industrie

    Informations forums :
    Inscription : Septembre 2015
    Messages : 36
    Points : 27
    Points
    27
    Par défaut
    Aaah, oui ça a l'air d'une piste très intéressante ce CUSTOM_CONTENT_FROM_VARIABLE. En plus cette fonctionnalité a été introduite avec la version 3.7 on dirait, ça a l'air acceptable comme version minimum pour un projet open source. Merci, je vais essayer de jouer avec ça.

  19. #19
    Membre éprouvé
    Femme Profil pro
    ..
    Inscrit en
    Décembre 2019
    Messages
    562
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Âge : 94
    Localisation : Autre

    Informations professionnelles :
    Activité : ..

    Informations forums :
    Inscription : Décembre 2019
    Messages : 562
    Points : 1 253
    Points
    1 253
    Par défaut
    P-S., je ne sais pas si ça ressort dans mon "code" précédent, mais si je ne m'abuse, en mode utilisation de la bibliothèque, il ne faut pas faire de "reinstanciation" explicite, puisque déjà fait à l'export.

  20. #20
    Nouveau membre du Club
    Homme Profil pro
    Ingénieur
    Inscrit en
    Septembre 2015
    Messages
    36
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Ingénieur
    Secteur : Industrie

    Informations forums :
    Inscription : Septembre 2015
    Messages : 36
    Points : 27
    Points
    27
    Par défaut
    Je viens de tester avec Visual Studio et MinGW sous Windows, ça fonctionne bien:

    Code CMakeLists : 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
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    cmake_minimum_required(VERSION 3.7)
    project(fcl CXX)
     
    include(GenerateExportHeader)
    ...
    set(FCL_EXPORT_MACRO_NAME "FCL_EXPORT")
    if(MSVC)
      set(EXPORT_INSTANTIATION_TEMPLATE_MACROS "
    #ifdef ${PROJECT_NAME}_EXPORTS
        /* We are building this library */
    #  define FCL_EXPLICIT_INSTANTIATION_DECLARATION
    #  define FCL_EXPLICIT_INSTANTIATION_DEFINITION ${FCL_EXPORT_MACRO_NAME}
    #else
        /* We are using this library */
    #  define FCL_EXPLICIT_INSTANTIATION_DECLARATION ${FCL_EXPORT_MACRO_NAME}
    #  define FCL_EXPLICIT_INSTANTIATION_DEFINITION
    #endif
    ")
    else()
      set(EXPORT_INSTANTIATION_TEMPLATE_MACROS "
    #define FCL_EXPLICIT_INSTANTIATION_DECLARATION ${FCL_EXPORT_MACRO_NAME}
    #define FCL_EXPLICIT_INSTANTIATION_DEFINITION
    ")
    endif()
    generate_export_header(${PROJECT_NAME}
      EXPORT_MACRO_NAME ${FCL_EXPORT_MACRO_NAME}
      EXPORT_FILE_NAME export.h
      CUSTOM_CONTENT_FROM_VARIABLE EXPORT_INSTANTIATION_TEMPLATE_MACROS
    )
    ...

    P-S., je ne sais pas si ça ressort dans mon "code" précédent, mais si je ne m'abuse, en mode utilisation de la bibliothèque, il ne faut pas faire de "reinstanciation" explicite, puisque déjà fait à l'export.
    Il ne faut pas faire de nouvelle définition explicite naturellement (ce qui ne peut pas arriver, c'est dans un unique cpp de la librairie). Mais il faut bien faire la déclaration explicite pour dire au compilo à l'utilisation: "hey t'embêtes pas à instancier ce machin, c'est déjà fait"

Discussions similaires

  1. Réponses: 1
    Dernier message: 28/02/2012, 12h54
  2. Variable extern avec librairie C
    Par razonback dans le forum Débuter
    Réponses: 3
    Dernier message: 20/03/2010, 19h19
  3. [XSLT][Templates][PHP] Que choisir pour gérer ses templates
    Par ChriGoLioNaDor dans le forum Langage
    Réponses: 8
    Dernier message: 03/09/2008, 20h49
  4. Réponses: 4
    Dernier message: 08/12/2006, 14h59
  5. [Besoin Conseils] Coder de manière élégante
    Par Mathusalem dans le forum Langage
    Réponses: 3
    Dernier message: 29/01/2006, 18h42

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