C++17 indique la disponibilité des en‐têtes (header)

Posté par  . Édité par Davy Defaud, Benoît Sibaud, claudex, ZeroHeure, Bruno Michel et Nils Ratusznik. Modéré par bubar🦥. Licence CC By‑SA.
Étiquettes :
38
2
déc.
2016
C et C++

Chaque jour de décembre a droit à sa surprise. Après l'ordre d'évaluation, aujourd'hui, le calendrier de l’Avent du C++ présente la Spécification Technique P0061 concernant une macro magique : #define __has_include.

Une personne déprime de ne plus rien comprendre au C++ et son collègue le rassure que LinuxFr.org publie le calendrier de l'Avent du C++ avec des explications pédagogiques

Sommaire

Série de dépêches C++

Cette dépêche fait partie de toute une série disponible également surle dépôt Git du Groupe des utilisateurs C++ francophone.

Publication Dépêche
20 août 2016 Les coulisses du standard C++
2 oct. 2016 Genèse du C++17
1ᵉʳ déc. 2016 C++17 fixe l’ordre d’évaluation des expressions
2 déc. 2016 C++17 indique la disponibilité des entêtes (header)
à venir… … d’autres dépêches …
en 2017 Faut‐il continuer à apprendre le C++ ?

Initialement, nous allions publier une grosse dépêche super trop longue. Puis, fin novembre, nous nous sommes ravisés et nous vous proposons plutôt une petite dépêche par jour, d’où l’idée du calendrier de l’Avent du C++.   (ღˇ◡ˇ)~♥

Spécification Technique

La macro __has_include est dans les cartons depuis plusieurs années. Le comité de normalisation du C++ a intégré cette fonctionnalité en amendant le TS P0061.

La macro

La macro __has_include() vérifie si l’en‐tête est disponible pour inclusion.

#if    __has_include(<filesystem>)
#  include           <filesystem>
#elif  __has_include(<experimental/filesystem>)
#  include           <experimental/filesystem>
#elif __has_include(<boost/filesystem.hpp>)
#  include          <boost/filesystem.hpp>
#else
#  error Ne trouve aucune en-tête filesystem
#endif

Dépendance optionnelle

Sans cette fonctionnalité, le code source avait moins de possibilités de s’adapter automatiquement à l’environnement de compilation. Pour les dépendances optionnelles, l’outil de compilation (autotools, CMake…) devaient détecter la présence de telle ou telle dépendance et passer au compilateur des macros pour activer ou désactiver des parties du code source.

Et sans cette complexité en amont, il est difficile de proposer du code C ou C++ qui gère des dépendances optionnelles : si l’en‐tête (header) d’une dépendance est absent, le compilateur arrête la compilation, car le code source tente d’inclure l’en‐tête introuvable de cette dépendance, même si la dépendance est optionnelle.

Chère lectrice, cher lecteur LinuxFr.org, tu as peut‐être un exemple pertinent en tête.
Tu souhaites déplacer la complexité de l’outil de compilation vers le code source ?
Fais‐nous part de tes idées dans les commentaires.

L’exemple ci‐dessous illustre l’utilisation de la macro __has_include(), mais aurait aussi pu se baser sur la détection de macros comme WIN32, _WIN64 ou MSCVER :

#if __has_include(<windows.h>)
#  include <windows.h>
   LONGLONG ticks1nano = []() {
     LARGE_INTEGER freq;
     QueryPerformanceFrequency(&freq);
     return freq.QuadPart / 1000'000;
   }();
   LONGLONG nanosecondes() {
     LARGE_INTEGER time;
     QueryPerformanceCounter(&time);
     return time.QuadPart/ticks1nano;
   }
#elif __has_include(<time.h>)
#  include <time.h>
   auto nanosecondes() {
      struct timespec ts;
      clock_gettime(CLOCK_MONOTONIC,&ts);
      return 1000'000'000 * ts.tv_sec + ts.tv_nsec;
   }
#else
#  error Ne trouve ni <windows.h> ni <time.h>
#endif

L’exemple suivant utilise également la macro __has_include().
C’est une possibilité pour une implémentation multi‐plate‐forme du futur Networking TS.

#if __has_include(<winsock2.h>)

#include <winsock2.h>

struct WindowsSocketImpl : AbstractSocket
{
  // implémentation Windows
};

using MySocket = WindowsSocketImpl;

#else

#include <sys/socket.h>

struct UnixSocketImpl : AbstractSocket
{
  // implémentation Unix
};

using MySocket = UnixSocketImpl;

#endif

// Usage
AbstractSocket * socket = new MySocket();

Roue de secours

Nous pouvons aussi imaginer l’utilisation de cette macro __has_include() pour sélectionner la bibliothèque à utiliser selon la disponibilité de différentes alternatives :

#if __has_include(<optional>)

#include <optional>
using MyOptional = std::optional;

#elif __has_include(<experimental/optional>)

#warning Utilise std::experimental::optional à la place de std::optional
#include <experimental/optional>  // roue de secours
using MyOptional = std::experimental::optional;

#elif __has_include(<boost/optional.hpp>)

#warning Utilise boost::optional à la place de std::optional
#include <boost/optional.hpp>     // roue de secours secondaire
using MyOptional = boost::optional;

#else
#  error Ne trouve ni <optional>, ni <experimental/optional>, ni <boost/optional>
#endif

Faut‐il continuer à apprendre le C++ ?

Panneau « Please Do Not Feed the Trolls » Panneau Troll barré
Ne pas nourrir les trolls Ne pas nourrir les trolls

Merci de nous aider à structurer et consolider les différentes idées sur cette question dans l’espace de rédaction collaboratif de LinuxFr.org : Faut‐il continuer à apprendre le C++ ?

Réutilisation

Le texte de cette dépêche est protégé par le droit d’auteur la gauche d’auteur et réutilisable sous licence CC BY-SA 4.0. Les images utilisées sont aussi sous licence libre (cliquer sur l’image pour plus de détails).

Donc, n’hésitez pas à réutiliser ce contenu libre pour créer, par exemple, des supports de formation, des présentations (Meetups), des publications sur d’autres blogs, des articles pour des magazines, et aussi un article C++17 sur Wikipédia dès que Wikipédia passera de la licence CC BY-SA 3.0 à la CC BY-SA 4.0 (le contenu de cette dépêche utilise la version la CC BY-SA 4.0).

Les auteurs

Par respect de la licence, merci de créditer les auteurs :

Continuer à améliorer ce document

Malgré tout le soin apporté, il reste certainement des oublis, des ambiguïtés, des fôtes… Bien que cette dépêche restera figée sur le site LinuxFr.org, il est possible de continuer à l’enrichir sur le dépôt Git du Groupe des utilisateurs C++ francophone (C++FRUG). C’est donc sur ce dépôt que se trouvent les versions les plus à jour.   (ღ˘⌣˘ღ)

Alors que cet article restera figé sur le site LinuxFr.org, il continuera d’évoluer sur le dépôt Git. Merci de nous aider à maintenir ce document à jour avec vos questions/suggestions/corrections. L’idée est de partager ce contenu libre et de créer/enrichir des articles Wikipédia quand la licence sera CC BY-SA 4.0.   ٩(•‿•)۶

La suite

La dépêche suivante nous dévoilera une autre nouveauté du C++17.

Chère lectrice, cher lecteur LinuxFr.org. Tu souhaites apporter ta pierre à cet édifice ? Rejoins‐nous dans l’espace de rédaction collaborative sur LinuxFr.org (un compte est nécessaire pour y accéder).

À suivre…

Aller plus loin

  • # Coquille

    Posté par  . Évalué à 2.

    s/difficil/difficile/g

  • # Double underscore

    Posté par  (site web personnel) . Évalué à 5.

    J'avais vu cette fonctionnalité il y a longtemps, mais je ne comprends toujours pas pourquoi elle a été intégrée avec un double underscore.

    Toutes les instructions du préprocesseurs n'en comportaient pas : #define #if #else #endif #elif #pragma #undef #__has_include // WTF??

    git is great because linus did it, mercurial is better because he didn't

    • [^] # Re: Double underscore

      Posté par  (site web personnel) . Évalué à 5. Dernière modification le 02 décembre 2016 à 11:47.

      surtout que "__" est sensé être reservé à unsage du compilo ou de l'utilisateur, bref le standard "promettait" de ne pas y toucher, si j'ai bien suivi :

      http://www.doc.ic.ac.uk/lab/cplus/c++.rules/chap5.html
      "The use of two underscores (`__') in identifiers is reserved for the compiler's internal use according to the ANSI-C standard. "

      A priori c'est ça dans la spec (que je n'ai pas) :

      17.4.3.1.2 Global names [lib.global.names]
      Certain sets of names and function signatures are always reserved to the implementation:
          Each name that contains a double underscore (_ _) or begins with an underscore followed by an uppercase letter (2.11) is reserved to the implementation for any use.
          Each name that begins with an underscore is reserved to the implementation for use as a name in the global namespace.165
      165) Such names are also reserved in namespace ::std (17.4.3.1).
      

      Bref, c'est étonnant en effet…

      toutefois, ta comparaison n'est pas bonne :

      Ce n'est pas "#__has_include" mais "#if __has_include", donc ça n'a pas grand chose à voir avec les '#'
      (__has_include est une macro)

    • [^] # Re: Double underscore

      Posté par  (site web personnel) . Évalué à 4. Dernière modification le 02 décembre 2016 à 11:53.

      (Oups, je n'avais pas vu que Zenitram avait déjà répondu pendant que je rédigeais cette réponse)

      __has_include ne s'utilise pas avec un croisillon (carré) comme #__has_include mais s'utilise comme toutes les macros du préprocesseur définies avec #define. Par exemple :

      #ifdef __has_include
      #  define DISPO_ENTETE_IOSTREAM __has_include(<iostream>)
      #endif
      
      #if __has_include(<stdio.h>)
      #  include <stdio.h>
      #endif

      Le préfixe avec le double tiret bas (underscore) permet d'indiquer que c'est un identifiant réservé (ne pas utiliser d'identifiant dans votre code source qui pourrait contenir deux tirets bas successifs __ quelque soit sa position, au début, au milieu ou à la fin).

      __has_include n'est pas un mot-clé du C++, mais une extension du compilateur qui est en cours de standardisation pour C++17.

      Commentaire sous licence Creative Commons Zero CC0 1.0 Universal (Public Domain Dedication)

    • [^] # Re: Double underscore

      Posté par  . Évalué à 2.

      En faisant une petite recherche Github sur "has_include" :

      https://github.com/search?l=C%2B%2B&p=2&q=has_include&type=Code&utf8=%E2%9C%93

      On voit qu'il y a déjà du code qui s'en sert comme un identifiant. Vu que c'est nécessairement une macro de préprocesseur, avoir #define has_include casserait le code existant, ce que le standard essaye d'éviter. S'ils avaient eu cette idée il y a 30 ans on n'en serait peut-être pas là :p

  • # L'intérêt ???

    Posté par  . Évalué à 5.

    Je ne vois franchement pas l'intérêt de cet apport.
    Pour contourner le problème de Windows ou Unix, il y a déjà la macro WIN32.
    Pour le reste on est le plus souvent obligé d'indiquer des chemins pour les fichiers d'en-tête.

    gcc -o test.o -I./include test.c
    

    ou

    gcc -o test.o -I./include/experimental test.c
    

    Et si c'est pour utiliser des API différentes, alors mieux vaut avoir des fichiers sources différents, ce qui se gère au niveau du Makefile.

    Et j'y vois de nouveaux inconvénients:
    - des nouveaux blocs de codes morts, au lieu de fichiers de code par cible.
    - des mélanges d'utilisation avec les macros déjà existantes, donc de nouvelles illisibilités dans le code.

    J'aurais préféré un système pour connaître la version d'une API. Là il y aurait un vrai plus et on aurait pu faire du ménage dans les préprocesseurs de Makefile. (adieu autotool et consort).

    Je ne trouve pas les exemples proposés convaincants.
    Je suis prêt à changer d'avis si vous m'en proposez d'autres.

    • [^] # Re: L'intérêt ???

      Posté par  (site web personnel) . Évalué à 4.

      Sur des pet projects mono-fichier et sans Makefile, cela permet d'inclure et utiliser simplement std::string_view qui sera dans expérimental ou pas p.ex. C'est sûr que ce n'est pas terrible, mais c'est un moyen de s'en sortir uniquement avec le préprocesseur.

      • [^] # Re: L'intérêt ???

        Posté par  . Évalué à 0.

        Merci, mais on ne peut pas parler de projet avec un seul fichier, et même là, la ligne de compilation suffit.

        • [^] # Re: L'intérêt ???

          Posté par  (site web personnel) . Évalué à 0.

          Injecter -Iunchemin dans la ligne de compilation c'est une chose, injecter en plus un -Dtrucquisededuitdufichiervisibledans$INCLUDE, ce n'est pas DRY: on répète des informations que l'outil aurait pu déduire pour nous.

          De plus, quid des projets sur gcc.goldbot. Comment savoir à l'avance si on va disposer d'une fonctionnalité ou d'autre autre?

  • # Cas "optional"

    Posté par  . Évalué à 2.

    Je m'en sers quasiment exactement comme ça dans mon code : https://github.com/OSSIA/i-score/blob/master/base/lib/iscore/tools/std/Optional.hpp

    Mais le optional boost et le optional std n'ont pas une API compatible, par exemple pour vider un optional c'est boost::none vs std::nullopt donc il faut définir des choses en plus :( :( :( :(

  • # Et le choix de compilation de l'utilisateur ?

    Posté par  . Évalué à 1.

    Si c’est géré au niveau du préprocesseur, comment peut-on empêcher l’utilisation de ces codes si on n’en veut pas ? Par exemple si on a deux implémentations d’une lib installées et qu’on souhaite que le programme à compiler en utilise une au lieu de l’autre, c’est possible de le gérer ?

    • [^] # Re: Et le choix de compilation de l'utilisateur ?

      Posté par  (site web personnel) . Évalué à 1.

      Pour moi ça se fait en amont dans la chaine de compilation où l'on va forcer quelle source d'une bibliothèque on veut utiliser. __has_include ne me parait pas du tout adapté. Les inline namespace à la limite, mais faut-il encore que la bibliothèque les utilise, et ça ce n'est pas gagné.

    • [^] # Re: Et le choix de compilation de l'utilisateur ?

      Posté par  (site web personnel) . Évalué à 1. Dernière modification le 05 décembre 2016 à 23:54.

      Si j'ai bien compris, tu veux compiler un logiciel qui utilises __has_include(<header>) et tu ne veux pas que ce code source choisisse le <header> à inclure => tu veux forcer l'inclusion du <header> de ton choix et non pas que ce soit le code source qui gère cette décision.

      Oui c'est possible, tu définis les chemins d'inclusion que tu souhaites. Par exemple, avec GCC et Clang, tu peux utiliser les options -I et -isystem (d'autres options sont disponibles pour contrôler l'inclusion des entêtes).

      Est-ce que cela répond à ta question ?

      Commentaire sous licence Creative Commons Zero CC0 1.0 Universal (Public Domain Dedication)

  • # Typo ×2

    Posté par  . Évalué à 3.

    roue de secour => roue de secours

Suivre le flux des commentaires

Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.