コンセプト[C++20]
コンセプト(concept)は、テンプレートに指定できる型を制約する機能です。C++におけるテンプレートでは、型パラメータに対して任意の型を指定できるので、もしその型で適切な処理ができない場合にはコンパイルエラーとなりました。しかしながら、エラーメッセージが直接的でないという問題がありました。たとえば以下のリストでは、加算演算子に適用できないconst char *などを与えると、コンパイルエラーとなります。
template<typename T>
T add(T a, T b) {
return a + b; // error: invalid operands to binary expression ('const char *' and 'const char *')
}
cout << add("Hello, ", "World!!") << endl; // note: in instantiation of function template specialization 'add<const char *>' requested here
そこでC++ 20で導入されたのがコンセプトです。コンセプトを型パラメータに指定したり、テンプレート自身に指定することで、あらかじめ受容できる型を制限できます。エラーメッセージもそれに対して出力されるので、分かりやすくなります。以下はシンプルなコンセプトの適用例です。
template<typename U> concept Integral = is_integral_v<U>; (1)
template<typename U> requires Integral<U> (2)
U add_integral(U a, U b) {
return a + b;
}
cout << add_integral(100, 200) << endl; (3)
cout << add_integral("Hello, ", "World!!") << endl; (4)
// error: no matching function for call to 'add_integral'
(1)がコンセプトを指定するメタ関数です。ここでは、std::is_integral_vを使って型Uが整数型なのかを判定しています(他に、浮動小数点数を判定するstd::is_floating_point_vなどがあります)。結果はIntegralに入ります。(2)においてtemplate文に続けてrequires式を記述し、型Uに対するIntegralの評価結果がtrueであれば、テンプレート自体を有効にするという意味になります。(3)は引数が整数なので問題なく受け入れられ、(4)は整数でないのでエラーとなります。
整数型であれば良いとか、シンプルな条件ではこのように型判定の定数式を使った方が簡便ですが、requires式には箇条書きのような形で条件を列挙できるので、より複雑な条件に対応することも可能です。
[NOTE]変数テンプレート[C++ 14]
テンプレートは関数やクラスなどに適用できるものでしたが、C++ 14で変数に対しても適用可能になりました。これにより、例えば型の異なる数値定数を一つの文で宣言することが可能になります。C++ 14以前は、関数やクラスを使って実現していた機能でした。この方法ですと定数式の実現に不便があったので、変数テンプレートが導入されました。以下のリストは、自然対数の底(ネイピア数)を変数テンプレートで宣言し、固定桁数で表示させています。
template <typename T> constexpr auto napier = static_cast<T>(2.718'281'828'459'045'235'360'287'471'352L); cout << fixed << setprecision(60) << napier<float> << endl; // 2.718281745910644531250000000000000000000000000000000000000000 cout << fixed << setprecision(60) << napier<double> << endl; // 2.718281828459045090795598298427648842334747314453125000000000 cout << fixed << setprecision(60) << napier<long double> << endl; // 2.718281828459045235428168107993940338928950950503349304199219
なお、ネイピア数の表現には、同じくC++ 14で導入された桁区切り文字( ' )を使用しています。桁区切り文字により、特に大きな整数でリテラルを読みやすく表現することができます。
モジュール
C++におけるライブラリの利用は#include文によるヘッダファイルの取り込みが、C言語誕生からの方法として使われてきました。しかしながらこの方法には、翻訳単位(プロプロセッサ処理後のコンパイラの入力となるコード)が大きくなる、取り込み位置より前にあるマクロなどの影響を受ける、といった問題がありました。そこでモジュール機能が提案され、C++ 20以降はimport文によるモジュール利用、export文による機能の公開というように、他言語と同様の使い方ができるようになりました。
モジュール[C++20]
まずはC++ 20で導入されたモジュール機能です。モジュールの単位は、モジュールユニットと呼ばれます。このうち、インタフェースユニットにエクスポートするモジュール名や構造体、関数などをexport文で宣言し、実装ユニットに構造体や関数の定義を記述します(マクロはエクスポートできません)。これらは同一のファイルでもよく、以下のリストでは同一ファイルで定義しています。
export module module_name;
namespace module_name {
export struct S {
int _a;
};
export S func(S s) {
return s;
}
}
利用側は、import文でインポートするモジュールを指定し、モジュール名にスコープ解決演算子(::)を付けてエクスポートされた識別名を指定します。
import module_name; module_name::S s; s._a = 1; module_name::func(s);
標準ライブラリのモジュール化[C++23]
C++ 20では言語仕様としてのモジュールが実装されましたが、標準ライブラリのモジュール化はC++ 23になってからです。std名前空間(C++ライブラリ)と、std.compat名前空間(C互換ライブラリ)がモジュールとして利用可能になりました。利用は、以下のようにimport文でそれぞれのモジュール名を指定するだけです。
import std; import std.compat; std::cout << 100 << std::endl;
ただし、前述の通りマクロはエクスポートできないので、標準ライブラリに含まれるマクロを使いたい場合には、別途#include文でヘッダファイル(iostream等)を取り込んであげる必要があります。
パターンマッチング[C++26?]
最後に、未リリースではありますが、将来のバージョンで実装予定とされているパターンマッチングを紹介します。パターンマッチングとは、switch文を発展させたような構文で、より複雑な条件に基づく仕訳を可能にする、他言語では次々と実装されてきた機能です。C++では、inspect文として実装されます。
従来のswitch文で以下のように記述していた仕訳があるとします(わざわざswitch文とするほどのものでもありませんが…)。
auto x = rand();
switch (x % 2) {
case 0: cout << "偶数です。"; break;
case 1: cout << "奇数です"; break;
}
これは、inspect文で書くと以下のようになります。「0」がパターン、{…}がマッチ時に実行すべき文です。
auto x = rand();
inspect (x % 2) {
0 => {cout << "偶数です。";}
1 => {cout << "奇数です";}
}
このような単純な仕訳ではinspect文の意味をあまり感じられないかも知れませんが、以下のリストのようにパターンがタプルなどになると、これはswitch文では対応できません。
auto bin = pair(1, 0);
inspect (bin) {
[0, 0] => { cout << "0"; }
[0, 1] => { cout << "1"; }
[1, 0] => { cout << "2"; }
[1, 1] => { cout << "3"; }
[__, __] => { cout << "不明な2進数"; }
};
まとめ
今回は、モジュールやパターンマッチング、そしてテンプレートをより便利に使えるコンセプトなどの機能をピックアップして紹介しました。
次回は最終回として、便利な基本ライブラリをピックアップして紹介します。
