コンテンツにスキップ

C++/機能テストマクロ

出典: フリー教科書『ウィキブックス(Wikibooks)』
< C++

C++の機能テストマクロ(Feature Test Macros)は、コンパイラが特定のC++の言語機能、属性、ヘッダーまたは標準ライブラリの存在をサポートしているかどうかを確認するためのマクロです。これらのマクロを使用することで、プログラマーはコード内で条件付きコンパイルを行うことができ、特定の環境やコンパイラのバージョンでのみ特定の機能を有効にすることができます。

機能テストマクロには、言語機能テストマクロ(__cpp_feature_name)、属性テストマクロ(__has_attribute(attr))、ヘッダー存在テストマクロ(__has_include(header))および標準ライブラリテストマクロ(__cpp_lib_feature_name)が含まれます。

言語機能テストマクロ(Language Feature Test Macros)
__cpp_feature_nameの形式を持ち、C++言語の特定の機能がサポートされているかどうかを示します。
例えば、__cpp_constexprマクロはconstexprキーワードがサポートされているかどうかを確認します。
属性テストマクロ(Attribute Test Macros)
__has_attribute(attr)の形式を持ち、コンパイラが指定された属性(attr)をサポートしているかどうかを確認します。
例えば、__has_attribute(deprecated)deprecated属性がサポートされているかどうかを確認します。
ヘッダー存在テストマクロ(Header Existence Test Macros)
__has_include(header)の形式を持ち、特定のヘッダー(header)がプリプロセッサによって利用可能かどうかを確認します。
例えば、__has_include(<iostream>)<iostream>ヘッダーが利用可能かどうかを確認します。
標準ライブラリテストマクロ(Standard Library Feature Test Macros)
__cpp_lib_feature_nameの形式を持ち、C++標準ライブラリの特定の機能がサポートされているかどうかを示します。
例えば、__cpp_lib_optionalは標準ライブラリの<optional>ヘッダーがサポートされているかどうかを確認します。

これらの機能テストマクロを使用することで、コード内でプラットフォーム固有の機能を適切に扱ったり、異なるコンパイラバージョンでの互換性を確保したりすることができます。

言語機能テストマクロ

[編集]

C++の__cpp_で始まる言語機能テストマクロは、C++のバージョンやコンパイラがサポートしている言語機能を示すためのマクロです。これらのマクロは、特定のC++の機能がどのバージョンから導入されたか、またはコンパイラがその機能をサポートしているかどうかを確認するために使用されます。

たとえば、__cpp_constexprマクロは、constexprというC++11から導入されたキーワードのサポートを示します。その値が定義されている場合、コンパイラはconstexprをサポートしています。これにより、コード内でconstexprを使用するかどうかを条件付きで決定することができます。

これらのマクロは通常、プリプロセッサディレクティブの#if#ifdefと組み合わせて使用され、コードのバージョンやコンパイラの機能を確認するために使用されます。

C++の言語機能テストマクロ
マクロ名 解説
__cpp_aggregate_bases 201603L 基底クラスの集約初期化
__cpp_aggregate_nsdmi 201304L 非静的メンバの初期化を含む集約初期化
__cpp_aggregate_paren_init 201902L 集約初期化における丸括弧初期化
__cpp_alias_templates 200704L 別名テンプレート
__cpp_aligned_new 201606L アラインメント指定new
__cpp_attributes 200809L 属性
__cpp_auto_cast 202110L autoキャスト
__cpp_binary_literals 201304L 2進リテラル
__cpp_capture_star_this 201603L thisを星でキャプチャ
__cpp_char8_t 202207L char8_t
__cpp_concepts 202002L コンセプト
__cpp_conditional_explicit 201806L 条件付きexplicit
__cpp_constexpr 202211L constexpr
__cpp_constexpr_dynamic_alloc 201907L 動的割り当てconstexpr
__cpp_constexpr_in_decltype 201711L decltype内のconstexpr
__cpp_consteval 202211L consteval
__cpp_constinit 201907L constinit
__cpp_decltype 200707L decltype
__cpp_decltype_auto 201304L decltype(auto)
__cpp_deduction_guides 201907L テンプレートの型推論
__cpp_delegating_constructors 200604L 委譲コンストラクタ
__cpp_designated_initializers 201707L 指定初期化子
__cpp_enumerator_attributes 201411L 列挙体の要素属性
__cpp_explicit_this_parameter 202110L 明示的なthisパラメータ
__cpp_fold_expressions 201603L 折り畳み式
__cpp_generic_lambdas 201707L ジェネリックラムダ
__cpp_guaranteed_copy_elision 201606L 保証されたコピー省略
__cpp_hex_float 201603L 16進浮動小数
__cpp_if_consteval 202106L if constexpr内のconsteval
__cpp_if_constexpr 201606L if constexpr
__cpp_impl_coroutine 201902L コルーチン実装
__cpp_impl_destroying_delete 201806L 破棄削除実装
__cpp_impl_three_way_comparison 201907L 三方比較実装
__cpp_implicit_move 202207L 暗黙的移動
__cpp_inheriting_constructors 201511L 継承コンストラクタ
__cpp_init_captures 201803L キャプチャの初期化
__cpp_initializer_lists 200806L 初期化子リスト
__cpp_inline_variables 201606L インライン変数
__cpp_lambdas 200907L ラムダ式
__cpp_modules 201907L モジュール
__cpp_multidimensional_subscript 202211L 多次元添字
__cpp_named_character_escapes 202207L 名前付き文字エスケープ
__cpp_namespace_attributes 201411L 名前空間の属性
__cpp_noexcept_function_type 201510L noexcept関数型
__cpp_nontype_template_args 201911L 非型テンプレート引数
__cpp_nontype_template_parameter_auto 201606L 非型テンプレートパラメータauto
__cpp_nsdmi 200809L 非静的メンバの初期化
__cpp_range_based_for 202211L 範囲ベースfor
__cpp_raw_strings 200710L 生文字列リテラル
__cpp_ref_qualifiers 200710L 参照修飾子
__cpp_return_type_deduction 201304L 戻り値型推論
__cpp_rvalue_references 200610L 右辺値参照
__cpp_size_t_suffix 202011L size_tサフィックス
__cpp_sized_deallocation 201309L サイズ指定解放
__cpp_static_assert 201411L 静的アサーション
__cpp_static_call_operator 202207L 静的呼び出し演算子
__cpp_structured_bindings 201606L 構造化束縛
__cpp_template_template_args 201611L テンプレートテンプレート引数
__cpp_threadsafe_static_init 200806L スレッドセーフな静的初期化
__cpp_unicode_characters 200704L Unicode文字
__cpp_unicode_literals 200710L Unicodeリテラル
__cpp_user_defined_literals 200809L ユーザー定義リテラル
__cpp_using_enum 201907L enumのusing宣言
__cpp_variable_templates 201304L 変数テンプレート
__cpp_variadic_templates 200704L 可変引数テンプレート
__cpp_variadic_using 201611L 可変引数using宣言

言語機能の解説

[編集]

基底クラスの集約初期化

[編集]

__cpp_aggregate_basesはC++の機能テストマクロの1つです。このマクロが定義されている場合、C++コンパイラは基底クラスの集約初期化をサポートしています。つまり、派生クラスの初期化リストに基底クラスのメンバを含めることができます。

例えば、以下のようなクラスがあるとします。

struct Base {
    int x;
};

struct Derived : Base {
    int y;
};

__cpp_aggregate_basesが有効な場合、以下のようにしてDerivedクラスを初期化することができます。

Derived d = {1, 2};

ここで、xが1に、yが2に初期化されます。

非静的メンバの初期化を含む集約初期化

[編集]

__cpp_aggregate_nsdmiは、C++14で導入された機能で、集約体(aggregate class)のメンバ変数に対してデフォルトのメンバ初期化子(default member initializer)を設定できるようになりました。

集約体とは、いわゆる平たい構造のクラスのことで、以下の条件を満たすものを指します:

  • 非staticデータメンバを持つ
  • 仮想関数を持たない
  • 非トリビアルなコンストラクタを持たない
  • プライベートやprotectedの非staticなデータメンバを持たない
  • ベースクラスを持たない
  • クラスの継承先がない

このような集約体のメンバ変数に対して、C++14以降ではデフォルトの初期化値を設定できるようになりました。

struct Point {
    int x = 0; // デフォルト値0で初期化
    int y = 0; 
};

Point p1; // p1.x = 0, p1.y = 0
Point p2 = {1, 2}; // p2.x = 1, p2.y = 2

デフォルトのメンバ初期化子を使うと、コンストラクタを書かなくてもメンバ変数が自動的に初期化されるため、簡潔で安全なコーディングが可能になります。

ただし注意点もあります。デフォルト値には、定数式しか指定できません。つまり、関数呼び出しや変数値は指定できません。また、デフォルトのメンバ初期化子は基本クラスよりも後に評価されます。

この機能は平たいデータ構造を多用するプログラムによく使われますが、安易な使用は避けたほうがよく、クラスの設計によってはコンストラクタを用いるほうが適切な場合もあります。


__cpp_aggregate_nsdmiは、C++の機能定義マクロの1つです。このマクロが定義されている場合、C++コンパイラは、非静的メンバの初期化を含む集約初期化をサポートしています。

集約初期化とは、配列や構造体などの集合体を初期化するための便利な構文です。非静的メンバの初期化を含む場合、それぞれのメンバの初期値が明示的に指定されている構造体や配列を、単一の初期化リストで初期化することができます。

例えば、以下のような構造体があるとします。

struct Point {
    int x;
    int y;
};

__cpp_aggregate_nsdmiが有効な場合、以下のようにしてPoint構造体を初期化することができます。

Point p = { .x = 10, .y = 20 };

この場合、.x.yという非静的メンバの名前が初期化リスト内で指定されています。

集約初期化における丸括弧初期化

[編集]

__cpp_aggregate_paren_initは、C++の機能定義マクロの1つです。このマクロが定義されている場合、C++コンパイラは、集約初期化における丸括弧初期化をサポートしています。

集約初期化は、配列や構造体などの集合体を初期化するための便利な構文です。丸括弧初期化は、集約初期化の一部であり、初期化リストを丸括弧で囲むことで行われます。

例えば、以下のような構造体があるとします。

struct Point {
    int x;
    int y;
};

__cpp_aggregate_paren_initが有効な場合、以下のようにしてPoint構造体を初期化することができます。

Point p = {10, 20};

この場合、初期化リストが丸括弧で囲まれていませんが、有効な機能定義マクロによって丸括弧で囲まれていない初期化リストが許容されています。

別名テンプレート

[編集]

__cpp_alias_templatesは、C++の機能定義マクロの1つであり、C++11で導入されたエイリアステンプレート(alias templates)のサポートを示します。

エイリアステンプレートは、既存のテンプレートを別の名前で定義する機能です。これにより、複雑な型名を簡潔にし、コードの可読性を向上させることができます。一般的な使用例は、以下のようになります。

template<typename T>
using Vec = std::vector<T>;

このようにすることで、Vecstd::vectorの別名として使用され、Vec<int>のように簡潔にベクターを表すことができます。

__cpp_alias_templatesが有効な場合、コンパイラはエイリアステンプレートの機能をサポートしています。

アラインメント指定new

[編集]

__cpp_aligned_newは、C++の機能定義マクロの1つで、C++17で導入された機能のサポートを示します。このマクロが定義されている場合、コンパイラはアラインメント指定されたnew表現(aligned new expressions)をサポートしています。

アラインメント指定されたnew表現は、特定のアラインメント要件を持つオブジェクトの動的な割り当てを可能にします。従来のnew演算子では、割り当てられたメモリのアラインメントはプラットフォームやコンパイラに依存しましたが、アラインメント指定されたnew表現を使用することで、開発者は特定のアラインメントを保証できます。

例えば、以下のようにアラインメント指定されたnew表現を使用できます。

int* ptr = new(std::align_val_t(64)) int;

この場合、new演算子の後にアラインメント値を指定することができます。std::align_val_tは、アラインメントを指定するための型であり、64は64バイトのアラインメントを指定しています。

__cpp_aligned_newが有効な場合、コンパイラはこのようなアラインメント指定されたnew表現をサポートしています。

属性

[編集]

__cpp_attributesは、C++の機能定義マクロの1つです。このマクロが定義されている場合、C++コンパイラはC++11で導入された属性(attributes)のサポートを示します。

属性は、ソースコードにメタデータを提供し、コンパイラや他のツールに対して情報を提供するための機能です。属性は、[[deprecated]]のような形式で表され、関数や変数、型、名前空間などに適用することができます。

たとえば、以下のようにして関数にdeprecated属性を適用することができます。

[[deprecated]] void oldFunction();

このようにすることで、oldFunctionが非推奨であることを示すことができます。

__cpp_attributesが有効な場合、コンパイラは属性の使用をサポートしており、属性を使用してコードにメタデータを付加できます。

個々のアトリビュートの使用可否は、__has_attribute() を使ってテストします。

autoキャスト

[編集]

__cpp_auto_castは、C++の機能定義マクロの1つです。このマクロが定義されている場合、C++コンパイラはC++23の新機能の一部として提案された機能である、auto(x)およびauto{x}という表記法のサポートを示します。

この機能は、式xをprvalue(一時オブジェクト)としてキャストすることを意味します。これは、xを値渡しの関数引数として渡すことと同等です。

動機:

  • prvalueコピーの取得が必要な場合
  • C++でオブジェクトのコピーを取得するための一般的な方法はauto a = x;ですが、このようなコピーはlvalueです。コード内でコピーをprvalueとして取得できると、目的をより正確に伝えることができます。
  • auto a = x.front();というステートメントは、変数を宣言するためのものであり、変数がコピーであることは宣言の特性です。一方、prvalueコピーを取得する式は、コピーを実行する明確な命令です。
  • auto(x.front())と書くことで、prvalueコピーを取得できます。これにより、Tが非自明な場合でも、T(x.front())のようなコードを書く必要がなくなります。
  • auto(x)を使用してprvalueコピーを取得することは、常に機能します。

この機能により、複雑なシナリオでもauto(x)を使用してprvalueコピーを取得することができます。

2進リテラル

[編集]

__cpp_binary_literals は、C++14で導入されたバイナリリテラルをサポートする機能を示すマクロ定義です。バイナリリテラルは、数値を直接バイナリ形式で表現するためのものです。これにより、特定のビットパターンを扱う場合や、二進数の値を使用する場合にコードの可読性が向上します。

バイナリリテラルの構文

C++14以降では、バイナリリテラルは 0b または 0B のプレフィックスで始まります。その後に0と1のシーケンスを続けることで数値を表現します。

int main() {
    int binary_literal = 0b1101;  // 2進数の1101、10進数では13
    std::cout << binary_literal << std::endl; // 出力: 13

    unsigned long long big_binary = 0b10101010101010101010101010101010;
    std::cout << big_binary << std::endl; // 大きな2進数リテラルもサポート
    return 0;
}
バイナリリテラルの利点
可読性の向上
特にビット操作を行う場合や、ビットフィールドを扱う場合に、バイナリリテラルを使うことでコードの意図が明確になります。
バグの減少
バイナリリテラルを使うことで、16進数や10進数への変換ミスを防ぐことができます。

以下にバイナリリテラルを使った例を示します。

基本的なバイナリリテラル
int main() {
    int a = 0b1010;   // 10進数の10
    int b = 0b1111;   // 10進数の15

    std::cout << "a: " << a << std::endl;  // 出力: a: 10
    std::cout << "b: " << b << std::endl;  // 出力: b: 15

    return 0;
}
ビットマスクとしてのバイナリリテラル

ビットマスクを使った操作にもバイナリリテラルは便利です。

#include <iostream>

int main() {
    const int FLAG1 = 0b0001; // ビット0
    const int FLAG2 = 0b0010; // ビット1
    const int FLAG3 = 0b0100; // ビット2
    const int FLAG4 = 0b1000; // ビット3

    int flags = FLAG1 | FLAG3; // ビット0とビット2をセット

    std::cout << "Flags: " << std::bitset<4>(flags) << std::endl; // 出力: Flags: 0101

    return 0;
}
まとめ

__cpp_binary_literals は、バイナリリテラルを使用するための機能を提供し、数値を二進数形式で直接表現できるようにします。これにより、ビット操作や特定のビットパターンを扱うコードの可読性と保守性が向上します。

thisを星でキャプチャ

[編集]

char8_t

[編集]

コンセプト

[編集]

条件付きexplicit

[編集]

constexpr

[編集]

__cpp_constexprは、C++の機能定義マクロの1つであり、C++のバージョンごとに定義されているconstexpr関連の機能を示します。

具体的な機能とバージョンごとの解説は以下の通りです。

  • 200704L(C++11):constexprキーワードが導入されました。これにより、関数や変数がコンパイル時に評価可能であることを示すことができます。
  • 201304L(C++14):constexprメソッドが非constメンバ関数でも使用可能になり、constexprの制約が緩和されました。
  • 201603L(C++17):constexprラムダ式が導入されました。
  • 201907L(C++20):constexpr関数内で仮想関数呼び出しやtry-catchブロック、dynamic_castやtypeidの使用、constexpr関数内でのトリビアルなデフォルト初期化、アセンブリ宣言が可能になりました。
  • 202002L(C++20):constexpr評価中にunionのアクティブメンバを変更できるようになりました。
  • 202110L(C++23):constexpr関数内で非リテラルな変数、ラベル、goto文の使用が可能になりました。constexpr関数および関数テンプレートの制約が緩和されました。
  • 202207L(C++23):constexpr関数内でstatic constexpr変数を使用できるようになりました。constexpr関数の制約が更に緩和されました。
  • 202211L(C++23):constexprキャストとしてvoid*からのキャストが可能になり、constexpr型消去に向けた取り組みが行われました。
  • 202306L(C++26):(まだ定義されていませんが)constexprキャストとしてvoid*からのキャストが可能になり、constexpr型消去に向けた取り組みが行われました。

これらの機能の追加や拡張により、constexprはC++のコンパイル時計算と静的解析の能力を向上させ、より多くの場面で使用されるようになりました。

動的割り当てconstexpr

[編集]

decltype内のconstexpr

[編集]

consteval

[編集]

constinit

[編集]

decltype

[編集]

__cpp_decltypeは、C++の機能定義マクロの1つであり、C++11で導入されたdecltypeキーワードのサポートを示します。

decltypeキーワードは、式の型を返すために使用されます。具体的には、式の評価結果の型を取得します。これにより、型を明示的に指定せずに、式の型を取得することができます。

具体的な使用例を示します。

#include <iostream>

int main() {
    int x = 5;
    decltype(x) y = 10; // xの型を取得して、それをyの型として使用
    std::cout << "y: " << y << std::endl; // yはint型となる

    const int& z = x;
    decltype(z) w = 15; // zの型を取得して、それをwの型として使用
    std::cout << "w: " << w << std::endl; // wはconst int&型となる

    return 0;
}

この例では、decltypeを使用して変数yおよびwの型を定義しています。decltype(x)int型であり、decltype(z)const int&型となります。

__cpp_decltypeが有効な場合、コンパイラはdecltypeキーワードの機能をサポートしています。

decltype(auto)

[編集]

__cpp_decltype_auto は、C++14で導入された機能で、通常の関数に対する戻り値型の推論をサポートします。この機能を利用することで、関数の戻り値の型をより柔軟に指定できます。具体的には、decltype(auto) を使うと、戻り値の型が自動的に推論され、より簡潔で明確なコードを書くことができます。

decltype(auto) の使用方法

decltype(auto) は、関数の戻り値型の推論に使用されます。これにより、関数の戻り値の型が式の型に正確に一致するようになります。以下に基本的な使い方を示します。

基本的な例
#include <iostream>
#include <vector>

decltype(auto) getFirstElement(std::vector<int>& v) {
    return v.front();
}

int main() {
    std::vector<int> myVector = {1, 2, 3, 4};
    std::cout << getFirstElement(myVector) << std::endl; // 出力: 1
    return 0;
}

この例では、getFirstElement 関数は std::vector<int> の先頭要素を返します。decltype(auto) を使用することで、戻り値の型が自動的に int& として推論されます。

auto との違い

decltype(auto)auto よりも強力で柔軟です。auto は式の値の型に基づいて推論されますが、decltype(auto) は式の型そのものに基づいて推論されます。これは参照やポインタを返す場合に特に有用です。

auto の場合
auto getFirstElement(std::vector<int>& v) {
    return v.front();
}

この場合、getFirstElement の戻り値の型は int となります。v.front() が返す int& から参照が外れてしまいます。

decltype(auto) の場合
decltype(auto) getFirstElement(std::vector<int>& v) {
    return v.front();
}

この場合、getFirstElement の戻り値の型は int& となり、参照が保持されます。

その他の例
参照を返す関数
#include <iostream>

int& getValue(int& x) {
    return x;
}

decltype(auto) forwardValue(int& x) {
    return getValue(x);
}

int main() {
    int a = 10;
    int& b = forwardValue(a);
    b = 20;
    std::cout << "a: " << a << std::endl; // 出力: a: 20
    return 0;
}

この例では、forwardValue 関数は getValue 関数の戻り値をそのまま返します。decltype(auto) を使うことで、戻り値の型が正確に int& として推論されます。

コンテナから要素を取得する関数
#include <iostream>
#include <map>

decltype(auto) getMappedValue(std::map<int, std::string>& m, int key) {
    return m[key];
}

int main() {
    std::map<int, std::string> myMap = {{1, "one"}, {2, "two"}};
    std::string& value = getMappedValue(myMap, 1);
    value = "ONE";
    std::cout << myMap[1] << std::endl; // 出力: ONE
    return 0;
}

この例では、getMappedValue 関数は std::map から要素を取得し、参照として返します。decltype(auto) により、戻り値の型が std::string& として正確に推論されます。

まとめ

__cpp_decltype_auto は、C++14で導入された機能で、decltype(auto) を使用して関数の戻り値型を柔軟に推論することができます。これにより、参照やポインタを返す場合にも型が正確に維持され、コードの可読性と保守性が向上します。

テンプレートの型推論

[編集]

委譲コンストラクタ

[編集]

__cpp_delegating_constructorsは、C++の機能定義マクロの1つで、C++11で導入された機能であるデリゲートコンストラクタ(delegating constructors)のサポートを示します。

デリゲートコンストラクタは、同じクラス内の別のコンストラクタを呼び出すことができる機能です。これにより、コンストラクタの共通した初期化処理を集約することができ、コードの再利用性や保守性を向上させることができます。

例えば、以下のようなクラスがあるとします。

class MyClass {
public:
    MyClass(int x) {
        // 何らかの初期化処理
    }

    MyClass() : MyClass(0) {} // デリゲートコンストラクタ
};

この例では、引数を受け取るコンストラクタとデフォルトコンストラクタが定義されています。デフォルトコンストラクタは、初期化リストで自身のクラス内の別のコンストラクタ(ここでは引数を受け取るコンストラクタ)を呼び出しています。これにより、初期化処理を再利用することができます。

__cpp_delegating_constructorsが有効な場合、コンパイラはこのようなデリゲートコンストラクタの機能をサポートしています。

指定初期化子

[編集]

列挙体の要素属性

[編集]

明示的なthisパラメータ

[編集]

折り畳み式

[編集]

ジェネリックラムダ

[編集]

保証されたコピー省略

[編集]

16進浮動小数

[編集]

if constexpr内のconsteval

[編集]

if constexpr

[編集]

コルーチン実装

[編集]

破棄削除実装

[編集]

三方比較実装

[編集]

暗黙的移動

[編集]

継承コンストラクタ

[編集]

__cpp_inheriting_constructors は、C++11で導入された機能で、派生クラスが基底クラスのコンストラクタを自動的に継承できるようになりました。

この機能の目的は、コードの冗長性を減らし、コンストラクタの継承を簡単にすることです。従来は、派生クラスでも基底クラスのコンストラクタを明示的に呼び出す必要がありましたが、__cpp_inheriting_constructorsを使えばその手間が省けます。

例えば、こういったコードがあるとします:

class Base {
public:
    Base(int x) : x_(x) {}
    int x_;
};

class Derived : public Base {
public:
    using Base::Base; // 基底クラスのコンストラクタを継承
    // 独自のコンストラクタやメンバ関数を追加できる
};

Derivedクラスでusing Base::Base;と記述することで、BaseクラスのコンストラクタBase(int x)を自動的に継承できます。つまり、Derivedクラスのオブジェクトを生成する際に Derived d(42); のように基底クラスのコンストラクタを呼び出せるようになります。

この機能は、コンストラクタの多重継承の問題を避けることもできるため、コードの保守性が向上します。ただし、不適切な使い方をするとコンパイルエラーやアクセス権の問題が発生する可能性があるので、注意が必要です。

キャプチャの初期化

[編集]

初期化子リスト

[編集]

__cpp_initializer_listsは、C++11で導入された機能で、初期化リストの構文とstd::initializer_listクラステンプレートを提供します。

初期化リスト構文

C++11以前は、配列の初期化は以下のように行われていました:

int arr[] = {1, 2, 3, 4, 5}; // OK
std::vector<int> vec = {1, 2, 3, 4, 5}; // エラー

しかし、__cpp_initializer_listsにより、任意の型の初期化が可能になりました:

std::vector<int> vec = {1, 2, 3, 4, 5}; // OK
std::map<string, int> map = {{"apple", 1}, {"banana", 2}}; // OK
std::initializer_list

std::initializer_listは初期化リストを表すクラステンプレートで、自作の型でも初期化リストを受け取れるようになります。

#include <initializer_list>

class MagicType {
public:
    MagicType(std::initializer_list<int> list) {
        // listから初期化処理を行う
    }
};

int main() {
    MagicType obj({1, 2, 3, 4, 5}); // std::initializer_listを渡せる
    return 0;
}

std::initializer_listは一度だけコピーされるため、一時オブジェクトの問題を回避できます。また、アクセスはconstポインタを介して行われるので、初期化リストの内容を変更することはできません。

この機能により、コードの記述が簡潔になり、可読性が向上します。しかし、パフォーマンスへの影響に注意が必要です。大量のデータを初期化リストで渡す場合は、コンストラクタで直接初期化するほうが効率的な場合があります。

インライン変数

[編集]

ラムダ式

[編集]

__cpp_lambdasは、C++11で導入されたラムダ式(lambda expression)の機能を表す機能テストマクロです。

ラムダ式とは、無名の関数オブジェクトを簡潔に記述できる仕組みです。従来のC++では無名関数を定義するのが面倒でしたが、ラムダ式の導入により簡単になりました。

ラムダ式の基本的な構文は以下のようになります:

[キャプチャー](パラメータリスト) -> 戻り値の型 { 関数ボディ }

簡単な例を示します:

#include <algorithm>
#include <iostream>
#include <vector>

auto main() -> int {
  std::vector<int> nums = {1, 2, 3, 4, 5};

  // ラムダ式を渡して偶数のみ出力
  std::for_each(nums.begin(), nums.end(), [](int n) {
    if (n % 2 == 0) {
      std::cout << n << " ";
    }
  });

  std::cout << std::endl;

  // 変数をキャプチャして使用
  auto multiplier = 2;
  std::transform(nums.begin(), nums.end(), nums.begin(),
                 [multiplier](int n) { return n * multiplier; });

  // nums の各要素が2倍になっている
  for (auto n : nums) {
    std::cout << n << " ";
  }

  return 0;
}
出力
2 4
2 4 6 8 10

ラムダ式は関数オブジェクトなので、関数に渡したり変数に代入したりできます。また、外部変数をキャプチャして使うことができ、無名でありながら状態を持てます。

この機能により、C++ではSTLアルゴリズムの引数としてインライン関数を簡潔に記述できるようになり、関数型プログラミングの表現力が格段に上がりました。一方で、ラムダ式の乱用はコードの可読性を下げる可能性もあるので注意が必要です。

モジュール

[編集]

多次元添字

[編集]

名前付き文字エスケープ

[編集]

名前空間の属性

[編集]

noexcept関数型

[編集]

非型テンプレート引数

[編集]

非型テンプレートパラメータauto

[編集]

非静的メンバの初期化

[編集]

__cpp_nsdmiは、C++11で導入された機能で、非静的データメンバの初期化子(initializer)を許可するものです。

従来のC++では、クラスのコンストラクタ内でデータメンバを初期化する必要がありました。例:

class MyClass {
public:
    MyClass(int x) : x_(x) {}
private:
    int x_;
};

しかし、__cpp_nsdmiにより、データメンバの定義時に直接初期値を設定できるようになりました。

class MyClass {
public:
    MyClass(int x) {}
private:
    int x_ = 42; // データメンバの初期化子
};

この機能には、主に2つの利点があります。

コードの簡潔化
コンストラクタ初期化リストを記述する必要がなくなり、コードがよりシンプルになります。
const データメンバの初期化
constデータメンバは初期化子でしか初期化できませんでした。__cpp_nsdmiにより、定義時に直接初期値を設定可能になりました。
class MyClass {
private:
    const int x_ = 42; // OK
};

ただし、注意点もあります。初期化子は、クラス定義時に評価されるため、関数の呼び出しや仮想関数の呼び出しは許可されません。また、初期化の順序は保証されないため、他のメンバに依存する初期化は危険です。

そのため、__cpp_nsdmiの利用は、単純な値や定数の初期化に限られることが一般的です。複雑な初期化はコンストラクタで行うほうが適切です。

初期化子は可読性が高く便利ですが、安全性の観点からも利用方法には気を付ける必要があります。

範囲ベースfor

[編集]

生文字列リテラル

[編集]

__cpp_raw_stringsは、C++の機能定義マクロの1つであり、C++11で導入された生文字列リテラル(raw string literals)のサポートを示します。

生文字列リテラルは、バックスラッシュをエスケープ文字として解釈しない文字列リテラルの一種です。このため、エスケープシーケンス(例えば\n\"など)をそのまま文字列として扱うことができます。

生文字列リテラルはダブルクオートと丸括弧で囲まれ、R"(...)"のように書かれます。...の部分にはエスケープシーケンスが解釈されません。

以下は生文字列リテラルの例です。

#include <iostream>

int main() {
    // 生文字列リテラルを使用した場合
    std::string str1 = R"(Hello\nWorld)";

    // 通常の文字列リテラルを使用した場合
    std::string str2 = "Hello\\nWorld";

    std::cout << "str1: " << str1 << std::endl; // Hello\nWorld
    std::cout << "str2: " << str2 << std::endl; // Hello\nWorld

    return 0;
}

__cpp_raw_stringsが有効な場合、コンパイラは生文字列リテラルの機能をサポートしています。

参照修飾子

[編集]

__cpp_ref_qualifiersは、C++の機能定義マクロの1つであり、C++11で導入された参照修飾子(ref-qualifiers)のサポートを示します。

参照修飾子は、メンバ関数に対して左辺値と右辺値の参照に対するオーバーロードを可能にします。これにより、メンバ関数がオブジェクトの左辺値と右辺値の両方に対して異なる動作を行うことができます。

参照修飾子は、メンバ関数の末尾に&(左辺値参照修飾子)または&&(右辺値参照修飾子)を付けて定義されます。

以下は参照修飾子の例です。

#include <iostream>

class MyClass {
public:
    void foo() & {
        std::cout << "Called on lvalue" << std::endl;
    }

    void foo() && {
        std::cout << "Called on rvalue" << std::endl;
    }
};

int main() {
    MyClass obj1;
    obj1.foo(); // foo() & が呼ばれる

    MyClass{}.foo(); // foo() && が呼ばれる

    return 0;
}

__cpp_ref_qualifiersが有効な場合、コンパイラは参照修飾子の機能をサポートしています。

戻り値型推論

[編集]

__cpp_return_type_deduction は、C++14で導入された機能を示すマクロ定義で、通常の関数に対する戻り値の型推論をサポートします。この機能により、関数の戻り値型を明示的に指定せずに、関数の実装に基づいて自動的に推論することができます。

戻り値型の推論 (Return Type Deduction)

C++14からは、関数定義において戻り値の型を auto とすることで、コンパイラがその関数の戻り値型を推論してくれるようになりました。これにより、コードが簡潔になり、関数の実装に変更があった場合でも戻り値型を自動的に更新してくれるため、保守性が向上します。

基本的な使用例
#include <iostream>

auto add(int a, int b) {
    return a + b;
}

int main() {
    std::cout << add(2, 3) << std::endl; // 出力: 5
    return 0;
}

この例では、add 関数の戻り値型は auto として宣言されています。コンパイラは、return a + b; の式の型に基づいて、戻り値の型を int と推論します。

複雑な型の戻り値

戻り値が複雑な型である場合でも、auto を使用することで型の指定が簡略化されます。

#include <vector>

auto createVector() {
    return std::vector<int>{1, 2, 3, 4};
}

int main() {
    auto vec = createVector();
    for (int n : vec) {
        std::cout << n << " "; // 出力: 1 2 3 4 
    }
    return 0;
}

この例では、createVector 関数は std::vector<int> を返しますが、戻り値型を auto とすることで型指定を省略しています。

decltype(auto)auto の違い

decltype(auto)auto よりもさらに強力で、戻り値が参照やポインタである場合にも正確に型を推論します。

#include <iostream>

int& getRef(int& x) {
    return x;
}

auto normalReturn(int& x) {
    return getRef(x);
}

decltype(auto) decltypeAutoReturn(int& x) {
    return getRef(x);
}

int main() {
    int a = 10;
    int& ref1 = normalReturn(a); // <code>ref1</code> の型は <code>int</code>
    int& ref2 = decltypeAutoReturn(a); // <code>ref2</code> の型は <code>int&</code>

    ref1 = 20; // <code>ref1</code> は <code>int</code> 型なので <code>a</code> は変わらない
    std::cout << "a: " << a << std::endl; // 出力: a: 10

    ref2 = 30; // <code>ref2</code> は <code>int&</code> 型なので <code>a</code> が変わる
    std::cout << "a: " << a << std::endl; // 出力: a: 30

    return 0;
}

この例では、normalReturn 関数は auto を使用しているため、戻り値の型は int になります。一方、decltypeAutoReturn 関数は decltype(auto) を使用しているため、戻り値の型は int& として正確に推論されます。

まとめ

__cpp_return_type_deduction は、C++14で導入された戻り値型の推論機能を示すマクロです。この機能により、関数の戻り値型を明示的に指定する必要がなくなり、関数の実装に基づいて自動的に推論されるため、コードの簡潔さと保守性が向上します。特に、autodecltype(auto) を使用することで、戻り値の型を柔軟に扱うことができるようになります。

右辺値参照

[編集]

__cpp_rvalue_referencesは、C++の機能定義マクロの1つであり、C++11で導入された右辺値参照(rvalue reference)のサポートを示します。

右辺値参照は、C++11で導入された概念で、通常の左辺値参照(lvalue reference)とは異なり、一時的なオブジェクトや一時的な式(右辺値)に対してのみ束縛できます。右辺値参照は、ムーブセマンティクスや完全転送などの機能を可能にし、パフォーマンスの向上や効率的なリソース管理を実現します。

具体的な使用例を示します。

void foo(int&& x) {
    // 右辺値参照を受け取る関数
}

int main() {
    int a = 5;
    foo(std::move(a)); // std::moveを使用してaの右辺値参照を渡す
    return 0;
}

この例では、foo関数は右辺値参照を受け取るため、std::moveを使用してaの右辺値参照を渡しています。これにより、aの所有権がfoo関数に移動し、ムーブセマンティクスが適用されます。

__cpp_rvalue_referencesが有効な場合、コンパイラは右辺値参照の機能をサポートしています。

size_tサフィックス

[編集]

サイズ指定解放

[編集]

静的アサーション

[編集]

静的呼び出し演算子

[編集]

構造化束縛

[編集]

テンプレートテンプレート引数

[編集]

スレッドセーフな静的初期化

[編集]

__cpp_threadsafe_static_initは、C++11で導入された機能で、スレッドセーフな静的変数の初期化と破棄を可能にします。

静的変数は、プログラムの実行時に初期化される変数で、関数の外で宣言されます。従来のC++では、静的変数の初期化がスレッド間で適切に行われるかは保証されていませんでした。つまり、複数のスレッドから同時にアクセスされると、初期化が競合状態になる可能性がありました。

__cpp_threadsafe_static_initにより、この問題が解決されました。C++11以降では、静的変数の初期化と破棄は、スレッドセーフな方法で自動的に行われます。

例えば、以下のようなコードを考えてみましょう:

#include <iostream>
#include <thread>

class MyClass {
public:
    MyClass() { std::cout << "Constructing MyClass" << std::endl; }
    ~MyClass() { std::cout << "Destructing MyClass" << std::endl; }
};

MyClass& getInstance() {
    static MyClass instance;
    return instance;
}

int main() {
    std::thread t1([]{ getInstance(); });
    std::thread t2([]{ getInstance(); });
    t1.join();
    t2.join();
    return 0;
}

この例では、getInstance()関数内で静的変数instanceが宣言されています。C++11以前のコンパイラでは、2つのスレッドが同時にgetInstance()を呼び出した場合、instanceの初期化が競合状態になる可能性がありました。

しかし、__cpp_threadsafe_static_initがあれば、C++11以降のコンパイラではこの問題は自動的に解決されます。つまり、上記のコードは正しく動作し、"Constructing MyClass"が一度だけ出力され、プログラム終了時に"Destructing MyClass"が出力されます。

この機能により、スレッドセーフなシングルトンクラスの実装が容易になり、並列プログラミングにおける安全性が向上しました。

Unicode文字

[編集]

__cpp_unicode_charactersは、C++の機能定義マクロの1つであり、C++11で導入された新しい文字型(char16_tおよびchar32_t)のサポートを示します。

C++11以前では、文字列を表す主要な型はchar型でしたが、この型は通常、8ビットであり、Unicode文字を表すのに十分ではありませんでした。C++11では、これに対応するために、16ビットおよび32ビットの符号なし整数型であるchar16_tおよびchar32_tが導入されました。

これらの型は、Unicode文字列を表現するために使用されます。char16_tはUTF-16エンコーディングに、char32_tはUTF-32エンコーディングに対応しています。これにより、プログラマーは広範なUnicode文字を正確に表現することができます。

具体的な使用例を示します。

#include <iostream>

int main() {
    char16_t u16_char = u'あ';
    char32_t u32_char = U'あ';

    std::cout << "UTF-16: " << u16_char << std::endl;
    std::cout << "UTF-32: " << u32_char << std::endl;

    return 0;
}

この例では、UTF-16とUTF-32のUnicode文字をそれぞれchar16_tおよびchar32_tで表しています。uプレフィックス(u'あ')はUTF-16文字列、Uプレフィックス(U'あ')はUTF-32文字列を示します。

__cpp_unicode_charactersが有効な場合、コンパイラはchar16_tおよびchar32_tの新しい文字型のサポートを示しています。

Unicodeリテラル

[編集]

__cpp_unicode_literalsは、C++の機能定義マクロの1つであり、C++11で導入されたUnicode文字列リテラルのサポートを示します。

Unicode文字列リテラルは、文字列リテラルの前にu8uU、またはLプレフィックスを付けることで表されます。

u8プレフィックス
UTF-8エンコードの文字列を表します。
uプレフィックス
UTF-16エンコードの文字列を表します。
Uプレフィックス
UTF-32エンコードの文字列を表します。
Lプレフィックス
ワイド文字列を表します。

以下は、Unicode文字列リテラルの例です。

#include <iostream>

int main() {
    std::cout << u8"こんにちは、世界!" << std::endl; // UTF-8文字列
    std::wcout << L"こんにちは、世界!" << std::endl; // ワイド文字列

    return 0;
}

__cpp_unicode_literalsが有効な場合、コンパイラはUnicode文字列リテラルの機能をサポートしています。

ユーザー定義リテラル

[編集]

__cpp_user_defined_literalsは、C++11で導入された機能で、ユーザ定義リテラル(User-defined literal)を使えるようになりました。

リテラルとは、プログラム中に直接埋め込まれた値のことで、整数リテラル(42)や文字列リテラル("hello")などがあります。__cpp_user_defined_literalsにより、新しい構文のリテラルをユーザが定義できるようになりました。

例えば、以下のようにバイト単位を表すリテラルを定義できます:

// ヘッダーファイル
constexpr long long operator"" _KB(long long kb) {
    return kb * 1024;
}

constexpr long long operator"" _MB(long long mb) {
    return mb * 1024 * 1024;
}

// 使用例
#include "user_literals.h"

long long filesize1 = 512_KB; // 512 * 1024
long long filesize2 = 4_MB;   // 4 * 1024 * 1024

operator""から始まる関数を定義し、任意の名前(_KB_MB)をリテラル接尾子として使用できます。これにより、値に対して意味のあるリテラルを付与できるので、可読性が高くなります。

ユーザ定義リテラルは様々な用途に使えます:

  • 単位リテラル (1.5_m, 30_deg)
  • 無限小/大リテラル (INFINITY_VAL)
  • 複素数リテラル (1.4_i)
  • 型指定リテラル (int32_t(42))

ユーザ定義リテラルの導入により、C++のプログラミングがより自然で表現力豊かになりました。一方で、乱用するとコードの可読性が下がる恐れもあるので、慎重に使う必要があります。

enumのusing宣言

[編集]

変数テンプレート

[編集]

__cpp_variadic_templates__cpp_variable_templates は、C++11およびC++14でそれぞれ導入されたテンプレート機能を示すマクロ定義です。これらの機能により、C++のテンプレートメタプログラミングが大幅に強化されました。

Variadic Templates (__cpp_variadic_templates)

Variadic Templates (可変長テンプレート) は、C++11で導入され、テンプレート引数の数を可変にすることができます。この機能を使うと、テンプレート関数やテンプレートクラスが任意の数の引数を受け取ることができます。

基本的な使用例
#include <iostream>

// 基本ケース:テンプレート関数の定義
template<typename... Args>
void print(Args... args) {
    (std::cout << ... << args) << '\n'; // Fold expression (C++17以降)
}

int main() {
    print(1, 2, 3, 4, 5);       // 出力: 12345
    print("Hello", ", ", "World", "!"); // 出力: Hello, World!
    return 0;
}

この例では、print 関数は任意の数の引数を受け取ることができ、各引数を標準出力に表示します。

テンプレートクラスでの使用例
#include <tuple>

// 可変長テンプレートを使ったテンプレートクラス
template<typename... Values>
class DataHolder {
public:
    DataHolder(Values... values) : data(values...) {}

    void print() const {
        printTuple(data);
    }

private:
    std::tuple<Values...> data;

    template<std::size_t... Is>
    void printTuple(const std::tuple<Values...>& tuple, std::index_sequence<Is...>) const {
        ((std::cout << std::get<Is>(tuple) << " "), ...) << '\n'; // Fold expression (C++17以降)
    }

    void printTuple(const std::tuple<Values...>& tuple) const {
        printTuple(tuple, std::index_sequence_for<Values...>{});
    }
};

int main() {
    DataHolder<int, double, std::string> holder(42, 3.14, "Hello");
    holder.print(); // 出力: 42 3.14 Hello 
    return 0;
}

この例では、DataHolder クラスは可変長テンプレート引数を利用して、任意の数の異なる型のデータを保持します。

Variable Templates (__cpp_variable_templates)

Variable Templates (変数テンプレート) は、C++14で導入され、テンプレート変数を定義できる機能です。これにより、定数や関数オブジェクトをテンプレートとして定義し、異なる型に対してインスタンス化することができます。

基本的な使用例
#include <iostream>

// テンプレート定数の定義
template<typename T>
constexpr T pi = T(3.1415926535897932385);

int main() {
    std::cout << pi<double> << '\n'; // 出力: 3.14159...
    std::cout << pi<float> << '\n';  // 出力: 3.14159f
    return 0;
}

この例では、pi テンプレート変数は型に応じた値を持つ定数として定義され、異なる型でインスタンス化されています。

まとめ
  • Variadic Templates (__cpp_variadic_templates) は、C++11で導入されたテンプレート機能で、テンプレート引数の数を可変にすることができます。これにより、任意の数の引数を受け取るテンプレート関数やテンプレートクラスを定義できます。
  • Variable Templates (__cpp_variable_templates) は、C++14で導入されたテンプレート機能で、テンプレート変数を定義できるようになります。これにより、異なる型に対して定数や関数オブジェクトをテンプレートとして定義し、使用できます。

これらの機能は、テンプレートメタプログラミングをより柔軟かつ強力にし、コードの再利用性と保守性を向上させることができます。

可変引数テンプレート

[編集]

__cpp_variadic_templatesは、C++の機能定義マクロの1つであり、C++11で導入された可変テンプレート(variadic templates)のサポートを示します。

可変テンプレートは、テンプレートのパラメータリストに可変長の引数を受け入れる機能です。これにより、関数やクラステンプレートを定義する際に、可変数の引数リストを受け入れることができます。

具体的な使用例を示します。

#include <iostream>

// テンプレート関数print()の定義
template<typename T>
void print(const T& value) {
    std::cout << value << std::endl;
}

// 可変テンプレート関数print()の定義
template<typename T, typename... Args>
void print(const T& value, const Args&... args) {
    std::cout << value << " ";
    print(args...); // 再帰的に引数を表示
}

int main() {
    print(1, 2.5, "Hello", 'a'); // 複数の引数を受け取り、表示する
    return 0;
}

この例では、print()関数が可変テンプレートとして定義されています。これにより、任意の数の引数を受け取ることができます。引数のリストを再帰的に処理して出力することで、可変数の引数リストを持つ関数を実装しています。

__cpp_variadic_templatesが有効な場合、コンパイラは可変テンプレートの機能をサポートしています。

可変引数using宣言

[編集]

属性テストマクロ

[編集]

__has_attribute()は、C++のプリプロセッサマクロで、特定の属性がコンパイラによってサポートされているかどうかを確認するために使用されます。このマクロは、指定された属性がサポートされている場合に1を返し、それ以外の場合に0を返します。

属性は、[[attr]]の形式で指定され、コンパイラに対してコードの特定の側面や挙動を指示するために使用されます。たとえば、[[deprecated]]属性は、コンパイラに対してその要素が非推奨であることを伝えます。

使用例を示します。

#if __has_attribute(deprecated)
    #define DEPRECATED(msg) [[deprecated(msg)]]
#else
    #define DEPRECATED(msg)
#endif

DEPRECATED("Use newFunction() instead")
void oldFunction();

この例では、__has_attribute()マクロを使用して、deprecated属性がサポートされているかどうかを確認しています。サポートされている場合、oldFunction()deprecated属性で修飾され、その引数には指定されたメッセージが表示されます。

ヘッダー存在テストマクロ

[編集]

__has_includeは、C++11で導入されたヘッダー存在テストマクロです。このマクロは、指定されたヘッダーファイルがプリプロセッサによって利用可能かどうかを確認します。このマクロは、条件付きコンパイルの際に特定のヘッダーファイルの存在をチェックするのに便利です。

__has_includeマクロは、指定されたヘッダーファイルが見つかれば1を、見つからなければ0を返します。また、ヘッダーファイルのパスを指定する際には、角括弧< >を使用するか、もしくはダブルクォーテーション" "を使用します。角括弧を使用すると、システムのヘッダーを検索しますが、ダブルクォーテーションを使用すると、プロジェクト内のヘッダーを検索します。

以下は、__has_includeマクロの使用例です。

#include <iostream> // システムヘッダー
#include <string>   // システムヘッダー

#ifdef __has_include
    #if __has_include("myheader.h")
        #include "myheader.h"
    #endif
#endif

この例では、myheader.hがプロジェクト内に存在する場合にのみ、そのヘッダーファイルをインクルードします。__has_includeマクロを使用することで、ヘッダーファイルの存在を事前に確認することができ、プロジェクトの構成に応じて異なるヘッダーファイルをインクルードすることができます。

標準ライブラリテストマクロ

[編集]

標準ライブラリテストマクロ(Standard Library Feature Test Macros)は、C++標準ライブラリの特定の機能がコンパイラによってサポートされているかどうかを確認するためのマクロです。これらのマクロを使用することで、プログラマーは標準ライブラリの特定の機能を使用できるかどうかを条件付きで判断し、互換性を確保したり、環境に応じたコードの分岐を行ったりすることができます。

標準ライブラリテストマクロの形式

標準ライブラリテストマクロは、__cpp_lib_feature_nameの形式を持ちます。各マクロは特定のライブラリ機能を示し、その機能がサポートされている場合はマクロが定義されています。これにより、コンパイラが特定の標準ライブラリ機能をサポートしているかどうかを簡単に確認することができます。

例えば、以下のようなマクロがあります。

__cpp_lib_optional
<optional>ヘッダーがサポートされているかどうかを確認します。
__cpp_lib_variant
<variant>ヘッダーがサポートされているかどうかを確認します。
__cpp_lib_filesystem
<filesystem>ヘッダーがサポートされているかどうかを確認します。
使用例

以下は、標準ライブラリテストマクロを使用して、<optional>ヘッダーがサポートされているかどうかを確認する例です。

#include <iostream>

#ifdef __cpp_lib_optional
#include <optional>
#else
#error "This compiler does not support <optional>"
#endif

int main() {
    std::optional<int> opt = 42;
    if (opt) {
        std::cout << "Optional has value: " << *opt << '\n';
    } else {
        std::cout << "Optional is empty\n";
    }
    return 0;
}

このコードでは、__cpp_lib_optionalマクロを使用してコンパイラが<optional>ヘッダーをサポートしているかどうかを確認しています。サポートされていない場合はコンパイルエラーを発生させます。これにより、標準ライブラリの特定の機能に依存するコードの互換性を確保することができます。

結論

標準ライブラリテストマクロを使用することで、プログラマーは特定のライブラリ機能のサポート状況を確認し、条件付きでコードを記述することができます。これにより、異なるコンパイラバージョンや環境に対して柔軟で互換性のあるコードを書くことが可能になります。