SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

Modern C++入門

Modern C++のモダンな言語仕様はもっとある! C++ 14から最新版までまとめて紹介

第7回 コンセプトとモジュールで安全・便利なプログラミング

コンセプト[C++20]

 コンセプト(concept)は、テンプレートに指定できる型を制約する機能です。C++におけるテンプレートでは、型パラメータに対して任意の型を指定できるので、もしその型で適切な処理ができない場合にはコンパイルエラーとなりました。しかしながら、エラーメッセージが直接的でないという問題がありました。たとえば以下のリストでは、加算演算子に適用できないconst char *などを与えると、コンパイルエラーとなります。

リスト:cencept.cpp
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で導入されたのがコンセプトです。コンセプトを型パラメータに指定したり、テンプレート自身に指定することで、あらかじめ受容できる型を制限できます。エラーメッセージもそれに対して出力されるので、分かりやすくなります。以下はシンプルなコンセプトの適用例です。

リスト:cencept.cpp

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文で宣言し、実装ユニットに構造体や関数の定義を記述します(マクロはエクスポートできません)。これらは同一のファイルでもよく、以下のリストでは同一ファイルで定義しています。

リスト:module_export.cpp

export module module_name;

namespace module_name {
    export struct S {
        int _a;
    };
    export S func(S s) {
        return s;
    }
}

 利用側は、import文でインポートするモジュールを指定し、モジュール名にスコープ解決演算子(::)を付けてエクスポートされた識別名を指定します。

リスト:module_import.cpp
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文でそれぞれのモジュール名を指定するだけです。

リスト:module_std.cpp

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進数"; }
};

まとめ

 今回は、モジュールやパターンマッチング、そしてテンプレートをより便利に使えるコンセプトなどの機能をピックアップして紹介しました。

 次回は最終回として、便利な基本ライブラリをピックアップして紹介します。

この記事は参考になりましたか?

連載通知を行うには会員登録(無料)が必要です。
既に会員の方はを行ってください。
Modern C++入門連載記事一覧

もっと読む

この記事の著者

WINGSプロジェクト 山内 直(WINGSプロジェクト ヤマウチ ナオ)

WINGSプロジェクトについて>有限会社 WINGSプロジェクトが運営する、テクニカル執筆コミュニティ(代表 山田祥寛)。主にWeb開発分野の書籍/記事執筆、翻訳、講演等を幅広く手がける。 2026年時点での登録メンバは約50名で、現在も執筆メンバを募集中。興味のある方は、どしどし応募頂きたい。著書記事多数。 RSS X: @WingsPro_info(公式)、@WingsPro_info/wings(メンバーリスト) Facebook <個人紹介>WINGSプロジェクト所属のテクニカルライター。出版社を経てフリーランスとして独立。ライター、エディター、デベロッパー、講師業に従事。屋号は「たまデジ。」。

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

山田 祥寛(ヤマダ ヨシヒロ)

静岡県榛原町生まれ。一橋大学経済学部卒業後、NECにてシステム企画業務に携わるが、2003年4月に念願かなってフリーライターに転身。Microsoft MVP for Visual Studio and Development Technologies。執筆コミュニティ「WINGSプロジェクト」代表。主な著書に「独習シリーズ(Java・C#・Python・PHP・Ruby・JSP&サーブレットなど)」「速習シリーズ(ASP.NET Core・Vue.js・React・TypeScript・ECMAScript、Laravelなど)」「改訂3版JavaScript本格入門」「これからはじめるLaravel実践入門」「はじめてのAndroidアプリ開発 Kotlin編 」他、著書多数

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/19709 2024/06/26 11:00

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー