Problemumgehungen für die Verwendung von __has_include, wenn es definiert sein kann oder nicht – Ist es gültig, __has_inC++

Programme in C++. Entwicklerforum
Anonymous
 Problemumgehungen für die Verwendung von __has_include, wenn es definiert sein kann oder nicht – Ist es gültig, __has_in

Post by Anonymous »

Oft möchte ich Präprozessor-Bedingungen mit __has_include verketten (definiert in C++17 und C23 und in früheren Versionen als Erweiterung von vielen Compilern unterstützt (GCC 4.9.2 und höher und Clang ab mindestens 3.0.0)). Ein einfaches Beispiel sieht etwa so aus:

Code: Select all

#if defined(__has_include) && __has_include("header.h")
#include "header.h"
#else
#include "backup-header.h"
#endif
Das Problem besteht darin, dass die Kompilierung für Compiler/Standards fehlschlägt, die __has_include nicht unterstützen. Die Prüfung „defined(__has_include) hilft uns nicht weiter, da die Phrase __has_include("header.h") überhaupt nicht syntaktisch gültig ist, wenn __has_include nicht definiert ist.
Die Dokumentation von GCC schlägt etwa Folgendes vor:

Code: Select all

#if defined __has_include
#  if __has_include ()
#    include 
#  endif
#endif
https://gcc.gnu.org/onlinedocs/cpp/_005 ... clude.html
Das Problem dabei ist, dass es schwierig wird, den Zweig #else zu verketten. Wenn wir es an unser Beispiel anpassen würden, müssen wir ein zusätzliches Makro definieren, um zu verfolgen, ob __has_include erfolgreich war, was den Code viel ausführlicher macht:

Code: Select all

#ifdef __has_include
#if __has_include("header.h")
#include "header.h"
#define HAS_INCLUDE_HEADER_H
#endif
#endif

#ifndef HAS_INCLUDE_HEADER_H
#include "backup-header.h"
#endif

#undef HAS_INCLUDE_HEADER_H
Wenn wir das nicht wollen, wäre eine naive Lösung, eine weitere Indirektionsebene hinzuzufügen:

Code: Select all

// This part can be done once in a common header
#ifdef __has_include
#define MY_HAS_INCLUDE(Arg) __has_include(Arg)
#else
#define MY_HAS_INCLUDE(Arg) 0
#endif

#if MY_HAS_INCLUDE("header.h")
#include "header.h"
#else
#include "backup-header.h"
#endif
Dies funktioniert auf vielen (allen?) Compilern, aber leider ist es nicht standardkonform und neueres Clang wird davor warnen. Die relevante Passage aus dem Standard lautet wie folgt:

Der Ausdruck, der die bedingte Einbeziehung steuert, muss ein ganzzahliger konstanter Ausdruck sein, mit der Ausnahme, dass Bezeichner (einschließlich derjenigen, die lexikalisch mit Schlüsselwörtern identisch sind) wie unten beschrieben interpretiert werden und null oder mehr definierte Makroausdrücke und/oder has-include-expressions und/oder has-attribute-expressions enthalten können unäre Operatorausdrücke.
...
Die Anweisungen #ifdef, #ifndef, #elifdef und #elifndef sowie der definierte bedingte Einschlussoperator behandeln __has_include und __has_cpp_attribute so, als wären sie die Namen definierter Makros. Die Bezeichner __has_include und __has_cpp_attribute dürfen in keinem Kontext erscheinen, der nicht in diesem Unterabschnitt erwähnt wird.

https://timsong-cpp.github.io/cppwp/n4950/cpp.cond
(C23 hat eine im Wesentlichen identische Klausel)
Mein Verständnis ist, dass dies besagt, dass „

Code: Select all

__has_include
kann nur im steuernden Ausdruck eines #if verwendet werden/

Code: Select all

#elif
-Direktive oder als zu definierendes Argument", aber ich bin mir über die genauen Grenzen ein wenig im Unklaren.

Ich habe über ein paar Problemumgehungen nachgedacht und mich gefragt, ob eine davon standardkonform ist und funktionieren würde.
(A) Die Definition von MY_HAS_INCLUDE als Makro ohne Argumente anstelle eines Makros, das ein Argument akzeptiert, bringt Clangs zum Schweigen Warnung, aber ich bezweifle, dass dies tatsächlich gültig ist, da wir den Ausdruck __has_include immer noch außerhalb einer #if-Bedingung schreiben. Besteht eine Chance, dass dies standardkonform ist?

Code: Select all

// This part can be done once in a common header
#ifdef __has_include
#define MY_HAS_INCLUDE __has_include
#else
#define HAS_INCLUDE_STUB(Arg) 0
#define MY_HAS_INCLUDE HAS_INCLUDE_STUB
#endif

#if MY_HAS_INCLUDE("header.h")
#include "header.h"
#else
#include "backup-header.h"
#endif
(B) Meine vielversprechendste Idee ist diese. Wäre es gültig, ein Makro wie folgt um den gesamten __has_include-Ausdruck zu wickeln?

Code: Select all

// This part can be done once in a common header
#ifdef __has_include
#define IF_HAS_INCLUDE_SUPPORTED(Arg) Arg
#else
#define IF_HAS_INCLUDE_SUPPORTED(Arg) 0
#endif

#if IF_HAS_INCLUDE_SUPPORTED(__has_include("header.h"))
#include "header.h"
#else
#include "backup-header.h"
#endif
(C) Wenn die Übergabe von __has_include an ein Makro überhaupt nicht zulässig ist, wäre dann so etwas gültig? In diesem Fall wird die Phrase __has_include nur dann als Argument an ein Makro übergeben, wenn sie nicht definiert ist. (Obwohl das so hässlich ist, dass es sich wahrscheinlich nicht lohnt, es tatsächlich zu tun)

Code: Select all

// This part can be done once in a common header
#ifdef __has_include
#define HAS_INCLUDE_TAKE_EMPTY_PAREN()
#define PRE_HAS_INCLUDE
#define POST_HAS_INCLUDE HAS_INCLUDE_TAKE_EMPTY_PAREN (
#else
#define HAS_INCLUDE_CONSUMER(Arg) 0
#define PRE_HAS_INCLUDE HAS_INCLUDE_CONSUMER (
#define POST_HAS_INCLUDE
#endif

#if PRE_HAS_INCLUDE __has_include("header.h") POST_HAS_INCLUDE )
#include "header.h"
#else
#include "backup-header.h"
#endif
(D) Eine bessere Version, die die beiden oben genannten Problemumgehungen kombiniert. In diesem Fall wird IF_HAS_INCLUDE_SUPPORTED(__has_include("header.h")) entweder auf 0 erweitert, wenn __has_include nicht unterstützt wird, oder (__has_include("header.h")) in Klammern, wenn dies der Fall ist. Und wenn __has_include unterstützt wird, wird es nie als Argument an ein Makro übergeben. Ist das gültig? Sicherlich muss es so sein.

Code: Select all

// This part can be done once in a common header
#ifdef __has_include
#define IF_HAS_INCLUDE_SUPPORTED
#else
#define IF_HAS_INCLUDE_SUPPORTED(Arg) (0)
#endif

#if IF_HAS_INCLUDE_SUPPORTED(__has_include("header.h"))
#include "header.h"
#else
#include "backup-header.h"
#endif
Was ich oft gesehen habe, ist die Definition von __has_include selbst, wenn es nicht definiert ist:

Code: Select all

// This part can be done once in a common header
#ifndef __has_include
#define __has_include(Arg) 0
#endif

#if __has_include("header.h")
#include "header.h"
#else
#include "backup-header.h"
#endif
Dies ist technisch gesehen immer noch ein Verstoß gegen den Standard (glaube ich), da Namen, die mit doppelten Unterstrichen beginnen, reserviert sind. Ich mag es auch nicht, weil es schwierig ist, zu sehen, was tatsächlich vor sich geht (da jemand, der späteren Code liest, möglicherweise nicht erkennt, dass wir __has_include selbst definiert haben), und es die Fähigkeit von späterem Code beeinträchtigt, zu überprüfen, ob __has_include tatsächlich mit definiertem unterstützt wird.
Welche meiner vorgeschlagenen Problemumgehungen (oder andere Lösungen, die ähnlich funktionieren) sind in C/C++ gültig, wenn überhaupt? Das Beispiel, das ich für die Verkettung von if-else gegeben habe, ist lediglich ein Beispiel. Ich interessiere mich für allgemeine Techniken zur Verwendung von __has_include in #if-Bedingungen, wenn es definiert sein kann oder nicht.

Quick Reply

Change Text Case: 
   
  • Similar Topics
    Replies
    Views
    Last post