C++20: Compiler-Funktionen mit Feature-Testing-Makros ermitteln

Ein relativ unbekanntes Feature in C + + 20 sind die Testmakros für Features. Sie geben die definitive Antwort, welches C++-Feature der Compiler unterstützt.​

In Pocket speichern vorlesen Druckansicht

(Bild: iX mit KI (Dall-E 3))

Lesezeit: 2 Min.
Von
  • Rainer Grimm
Inhaltsverzeichnis

C++20 bringt die Featuretestmakros mit, die den verwendeten Compiler daraufhin untersuchen, welche Features er unterstützt.

Wer die neuesten C++-Features ausprobiert, erhält oft Fehlermeldungen. Jetzt stellt sich die Frage: Wer ist dafür verantwortlich?

  1. Hat man selbst einen Fehler gemacht?
  2. Unterstützt der verwendete Compiler dieses Feature?
  3. Ist ein Compiler-Bug aufgetreten?
Modernes C++ – Rainer Grimm

Rainer Grimm ist seit vielen Jahren als Softwarearchitekt, Team- und Schulungsleiter tätig. Er schreibt gerne Artikel zu den Programmiersprachen C++, Python und Haskell, spricht aber auch gerne und häufig auf Fachkonferenzen. Auf seinem Blog Modernes C++ beschäftigt er sich intensiv mit seiner Leidenschaft C++.

Option 3 tritt eher selten auf, also muss man nur eine Frage beantworten: Unterstützt der Compiler dieses Feature?

Diese Frage ist dank der cppreference und der Feature-Testmakros leicht zu beantworten.

Wie so oft gibt cppreference.com/compiler_support Antworten auf Fragen zur Kernsprache und Bibliothek, einschließlich der C++11-Standards bis hin zu C++26.

Die folgenden Tabellen geben einen Überblick über die Unterstützung der Kernsprache und der Bibliothek für den C++26-Standard.

Wenn die Antworten nicht spezifisch genug sind, helfen die Featuretestmakros in C++20.

Mit dem Header <version> kann man den Compiler nach seiner Unterstützung für C++11 oder höher fragen. Man kann nach Attributen, Features der Kernsprache oder der Bibliothek fragen. <version> hat mehr als 300 Makros definiert, die sich zu einer Zahl erweitern, wenn das Feature implementiert ist. Die Zahl steht für das Jahr und den Monat, in dem das Feature zum C++-Standardentwurf hinzugefügt wurde. Dies sind die Nummern für static_assert, Lambdas und Concepts.

__cpp_static_assert  200410L
__cpp_lambdas  200907L
__cpp_concepts 201907L

Die Seite Feature Testing Macros führt alle Makros auf.

Das folgende Programm, das ich von cppreference übernommen und angepasst habe, zeigt alle Makros der Kernsprache C++20 an. Das Flag /Zc:__cplusplus aktiviert das Featuretestmakro unter Windows.

// featureTesting20.cpp
// from cppreference.com

#if __cplusplus < 201100
#  error "C++11 or better is required"
#endif
 
#include <algorithm>
#include <cstring>
#include <iomanip>
#include <iostream>
#include <string>
 
#ifdef __has_include
# if __has_include(<version>)
#   include <version>
# endif
#endif
 
#define COMPILER_FEATURE_VALUE(value) #value
#define COMPILER_FEATURE_ENTRY(name) { #name, COMPILER_FEATURE_VALUE(name) },
 
#ifdef __has_cpp_attribute
# define COMPILER_ATTRIBUTE_VALUE_AS_STRING(s) #s
# define COMPILER_ATTRIBUTE_AS_NUMBER(x) COMPILER_ATTRIBUTE_VALUE_AS_STRING(x)
# define COMPILER_ATTRIBUTE_ENTRY(attr) \
  { #attr, COMPILER_ATTRIBUTE_AS_NUMBER(__has_cpp_attribute(attr)) },
#else
# define COMPILER_ATTRIBUTE_ENTRY(attr) { #attr, "_" },
#endif
 
// Change these options to print out only necessary info.
static struct PrintOptions {
    constexpr static bool titles               = 1;
    constexpr static bool attributes           = 1;
    constexpr static bool general_features     = 1;
    constexpr static bool core_features        = 1;
    constexpr static bool lib_features         = 1;
    constexpr static bool supported_features   = 1;
    constexpr static bool unsupported_features = 1;
    constexpr static bool sorted_by_value      = 0;
    constexpr static bool cxx11                = 1;
    constexpr static bool cxx14                = 1;
    constexpr static bool cxx17                = 1;
    constexpr static bool cxx20                = 1;
    constexpr static bool cxx23                = 0;
}   print;
 
struct CompilerFeature {
    CompilerFeature(const char* name = nullptr, const char* value = nullptr)
        : name(name), value(value) {}
    const char* name; const char* value;
};
 
static CompilerFeature cxx20[] = {
COMPILER_FEATURE_ENTRY(__cpp_aggregate_paren_init)
COMPILER_FEATURE_ENTRY(__cpp_char8_t)
COMPILER_FEATURE_ENTRY(__cpp_concepts)
COMPILER_FEATURE_ENTRY(__cpp_conditional_explicit)
COMPILER_FEATURE_ENTRY(__cpp_consteval)
COMPILER_FEATURE_ENTRY(__cpp_constexpr)
COMPILER_FEATURE_ENTRY(__cpp_constexpr_dynamic_alloc)
COMPILER_FEATURE_ENTRY(__cpp_constexpr_in_decltype)
COMPILER_FEATURE_ENTRY(__cpp_constinit)
COMPILER_FEATURE_ENTRY(__cpp_deduction_guides)
COMPILER_FEATURE_ENTRY(__cpp_designated_initializers)
COMPILER_FEATURE_ENTRY(__cpp_generic_lambdas)
COMPILER_FEATURE_ENTRY(__cpp_impl_coroutine)
COMPILER_FEATURE_ENTRY(__cpp_impl_destroying_delete)
COMPILER_FEATURE_ENTRY(__cpp_impl_three_way_comparison)
COMPILER_FEATURE_ENTRY(__cpp_init_captures)
COMPILER_FEATURE_ENTRY(__cpp_modules)
COMPILER_FEATURE_ENTRY(__cpp_nontype_template_args)
COMPILER_FEATURE_ENTRY(__cpp_using_enum)
};


constexpr bool is_feature_supported(const CompilerFeature& x) {
    return x.value[0] != '_' && x.value[0] != '0' ;
}
 
inline void print_compiler_feature(const CompilerFeature& x) {
    constexpr static int max_name_length = 44; //< Update if necessary
    std::string value{ is_feature_supported(x) ? x.value : "------" };
    if (value.back() == 'L') value.pop_back(); //~ 201603L -> 201603
    // value.insert(4, 1, '-'); //~ 201603 -> 2016-03
    if ( (print.supported_features && is_feature_supported(x))
        || (print.unsupported_features && !is_feature_supported(x))) {
            std::cout << std::left << std::setw(max_name_length)
                      << x.name << " " << value << '\n';
    }
}
 
template<size_t N>
inline void show(char const* title, CompilerFeature (&features)[N]) {
    if (print.titles) {
        std::cout << '\n' << std::left << title << '\n';
    }
    if (print.sorted_by_value) {
        std::sort(std::begin(features), std::end(features),
            [](CompilerFeature const& lhs, CompilerFeature const& rhs) {
                return std::strcmp(lhs.value, rhs.value) < 0;
            });
    }
    for (const CompilerFeature& x : features) {
        print_compiler_feature(x);
    }
}
 
int main() {
    
    if (print.cxx20 && print.core_features) show("C++20 CORE", cxx20);
   
}

Die folgenden Screenshots aus dem Compiler Explorer zeigen die C++20 Unterstützung der Compiler GCC 8.1, 10.1 und 14.1.

  • GCC 8.1
  • GCC 10.1
  • GCC 14.1

In meinem nächsten Artikel setze ich meine Reise durch C++ 23 fort. (rme)