次の方法で共有



April 2016

Volume 31 Number 4

Visual C++ - C++ の将来に向けたマイクロソフトの後押し

Kenny Kerr | April 2016

Visual C++ は時代遅れと言われています。最新かつ最高の C++ 機能が必要ならば、Clang や GCC を使用すべきだという声も聞こえてきます。そこで今回は、現状に対する変化、根本にある問題、力関係の乱れなどについて考えてみようと思います。たしかに Visual C++ コンパイラには非常に古いコード ベースが含まれ、マイクロソフトの C++ チームは新機能をすばやく追加するのに四苦八苦しています (goo.gl/PjSC7v、英語)。ですが、C++ 言語や標準ライブラリへの新たな提案の多くの根底にある Visual C++ に変化が起こり始めています。ここでは、Visual C++ Update 2 リリースに含まれる新機能や強化機能の中から、特に魅力的だと感じる機能や、手元にあるこのコンパイラがまだ役に立つことを示す機能を紹介します。

モジュール

マイクロソフトの数人の開発者、特に Gabriel Dos Reis と Jonathan Caves は、コンポーネント化のサポートを C++ 言語に直接追加する設計に取り組んでいます。この設計から、プリコンパイル済みヘッダーのように、ビルドのスループットが向上することも目論んでいます。「C++ 向けモジュール システム」と名付けたこの設計は、C++ 17 に提案されています。また、新しい Visual C++ コンパイラは概念実証を提供し、C++ でのモジュールのワーキング実装を開始します。モジュールは、標準 C++ を使用する開発者が作成と使用の両方を非常に簡単かつ自然に行えるように設計されています。Visual C++ Update 2 をインストールし、開発者コマンド プロンプトを開いて、ここで示す方法を実践してみてください。機能はまだかなり実験段階で、IDE はサポートされていません。使い始める最善の方法としては、コマンド プロンプトから直接コンパイラを使用します。

それでは、次に示すような既存の C++ ライブラリがあり、これをモジュールとして配布するとします。

C:\modules> type animals.h
#pragma once
#include <stdio.h>
inline void dog()
{
  printf("woof\n");
}
inline void cat()
{
  printf("meow\n");
}

また、このライブラリを利用する魅力的なサンプル アプリもあります。

C:\modules> type app.cpp
#include "animals.h"
int main()
{
  dog();
  cat();
}

C++ の開発者たちからプレッシャーがあり、printf を使用するはためらわれるのですが、その比類なきパフォーマンスからその魅力に抗うことができません。そこで、他の形式の I/O よりも printf を好んで使用していることを隠ぺいするために、ライブラリをモジュールに変換することにします。まずは、モジュール インターフェイスを作成します。

C:\modules> type animals.ixx
module animals;
#include "animals.h"

もちろん、モジュール インターフェイス ファイルの内部で cat 関数と dog 関数を定義してもかまいませんが、それらの関数をインクルードしても問題ありません。module 宣言は、その宣言以降がそのモジュールの内容であることをコンパイラに伝えますが、その後の宣言がモジュールのインターフェイスの一部としてすべてエクスポートされるわけではありません。現時点では、animals.h でインクルードされている stdio.h ヘッダーがエクスポートしない限り、このモジュールからは何もエクスポートされません。また、module 宣言の前に stdio.h をインクルードしても、同様にエクスポートを防ぐことができます。では、このモジュール インターフェイスで実際にパブリック名を一切宣言しないとすると、モジュール外部で利用するものをエクスポートするにはどうすればよいでしょう。エクスポートするには export キーワードを使用します。この export キーワードと、module キーワード、import キーワードが C++ 言語への追加機能で唯一考慮すべきものです。ここでは、この新しい言語機能がいかにシンプルかを取り上げます。

今回エクスポートできるのは、cat 関数と dog 関数です。そこで、animals.h ヘッダーを更新し、export 指定子を付けてこの 2 つの関数を宣言します。

C:\modules> type animals.h
#pragma once
#include <stdio.h>
export inline void dog()
{
  printf("woof\n");
}
export inline void cat()
{
  printf("meow\n");
}

これで、コンパイラの experimental:module オプションを使用して、モジュール インターフェイス ファイルをコンパイルできるようになります。

C:\modules> cl /c /experimental:module animals.ixx

/c オプションを指定して、コードをリンクするのではなく、単にコードをコンパイルするようコンパイラに指示しています。この段階で、リンカーに実行可能ファイルの作成を試行させることに意味はありません。module オプションは、モジュールのインターフェイスと実装についてバイナリ形式で記述されたメタデータを含むファイルを生成するようコンパイラに指示します。このメタデータは機械語コードではなく、C++ 言語構成のバイナリ表現です。ただし、ソース コードでもありません。見方によって異なりますが、長所も短所もあります。長所としては、モジュールをインポートする側のアプリはコードを解析し直す必要がないため、ビルドのスループットが向上します。一方、Visual Studio やその IntelliSense エンジンのような従来からあるツールが表示、解析するソース コードが存在しません。つまり、Visual Studio などのツールに、モジュール内のコードを利用、表示する方法を指示しなければなりません。さいわい、モジュール内部のコードやメタデータはオープン形式で格納されるため、その形式に合わせてツールを更新できます。

これで、アプリはライブラリ ヘッダーではなくモジュールを直接インポートできるようになります。

C:\modules> type app.cpp
import animals;
int main()
{
  dog();
  cat();
}

import 宣言は、対応するモジュール インターフェイス ファイルを探すようコンパイラに指示します。次に、コンパイラは見つかったファイルと、アプリに存在する他の include を使用して、dog 関数と cat 関数を解決します。animals モジュールは柔らかい毛に包まれた動物の関数のペアをエクスポートするため、同じ module コマンドライン オプションを使用してアプリを再コンパイルできます。

C:\modules> cl /experimental:module app.cpp animals.obj

今回は、実際には実行可能ファイルを生成することになるため、コンパイラがリンカーを呼び出せるようにしています。import キーワードがまだ正式なものではないため、experimental:module オプションが依然として必要です。さらに、リンカーは、モジュールのコンパイル時に生成されるオブジェクト ファイルも必要とします。繰り返しになりますが、モジュールのメタデータを含む新しいバイナリ形式は、実際には「コード」ではなく、エクスポートされた宣言、関数、クラス、テンプレートなどの記述にすぎません。モジュールを使用するアプリを実際にビルドする時点で、リンカーがコードを実行可能ファイルにアセンブルする作業を実行できるように、オブジェクト ファイルが必要です。すべてうまくいったら、他の実行可能ファイルと同じように実行できる実行可能ファイルが用意されます。目指す結果は、ヘッダーのみのライブラリを使用する最初のアプリとなんら違いはありません。付け加えれば、モジュールは DLL ではありません。

ここで、大きいライブラリに取り組むことを考えてみます。各宣言に export を追加するという考えはあまり魅力的ではありません。さいわい、export 宣言がエクスポートするのは関数だけではありません。一連の宣言を中かっこのペアを囲んでエクスポートするのが、1 つのオプションです。

C:\modules> type animals.h
#pragma once
#include <stdio.h>
export
{
  inline void dog()
  {
    printf("woof\n");
  }
  inline void cat()
  {
    printf("meow\n");
  }
}

これは新しいスコープを導入するものではなく、かっこ内に囲んだ宣言をエクスポート用にグループ化しているだけです。もちろん、グローバル スコープに一連の宣言を含むライブラリを作成する自尊心に乏しい C++ プログラマもいます。それならば、名前空間の内部で dog 関数と cat 関数を宣言する今回の animals.h ヘッダーのほうがはるかに好ましく、ごくシンプルに名前空間全体をエクスポートできます。

C:\modules> type animals.h
#pragma once
#include <stdio.h>
export namespace animals
{
  inline void dog()
  {
    printf("woof\n");
  }
  inline void cat()
  {
    printf("meow\n");
  }
}

ヘッダーのみのライブラリからモジュールに移行するもう 1 つの小さなメリットは、stdio.h をモジュールのインターフェイスに含めていないので、アプリが誤って stdio.h に依存することがなくなります。ヘッダーのみのライブラリが、アプリが直接使用することのない実装の詳細を含む名前空間を入れ子にしてインクルードしていたらどうなるでしょう。図 1 は、このようなライブラリの典型的な例を示しています。

図 1 実装名前空間を含むヘッダーのみのライブラリ

C:\modules> type animals.h
#pragma once
#include <stdio.h>
namespace animals
{
  namespace impl
  {
    inline void print(char const * const message)
    {
      printf("%s\n", message);
    }
  }
  inline void dog()
  {
    impl::print("woof");
  }
  inline void cat()
  {
    impl::print("meow");
  }
}

このライブラリを利用する側は、実装名前空間に含まれるものとの依存関係がないことは分かっています。当然、コンパイラは、開発者が間違えて次のようにしてもそれを阻止しません。

C:\modules> type app.cpp
#include "animals.h"
using namespace animals;
int main()
{
  dog();
  cat();
  impl::print("rats");
}

このモジュールは役立つでしょうか。もちろんです。しかし、モジュールの設計は、機能をできる限り小さくシンプルな状態に保つことを基本にしています。これでは、宣言をエクスポートすると、すべてが無条件にエクスポートされてしまいます。

C:\modules> type animals.h
#pragma once
#include <stdio.h>
export namespace animals
{
  namespace impl
  {
    // Sadly, this is exported, as well
  }
  // This is exported
}

さいわい、ライブラリの名前空間の構造を保持した状態で animals::impl 名前空間が個別に宣言されるように、コードを再構成できます (図 2 参照)。

図 2 ライブラリの名前空間構造の保持

C:\modules> type animals.h
#pragma once
#include <stdio.h>
namespace animals
{
  namespace impl
  {
    // This is *not* exported -- yay!
  }
}
export namespace animals
{
  // This is exported
}

これで必要なのは、Visual C++ で入れ子になった名前空間の定義を実装することだけになります。これにより、かなり見栄えがよくなり、多くの入れ子になった名前空間を含むライブラリの管理が非常に簡単になります。

C:\modules> type animals.h
#pragma once
#include <stdio.h>
namespace animals::impl
{
  // This is *not* exported -- yay
}
export namespace animals
{
  // This is exported
}

この機能が Visual C++ Update 3 で正式採用されることを期待しましょう。現状、今回の animals.h ヘッダーは単にこのヘッダーをインクルードする既存のアプリを停止させ、おそらくは、まだモジュールをサポートしないコンパイラを使用してビルドされます。モジュールに徐々に移行していく間も既存のライブラリ ユーザーをサポートする必要がある場合、面倒なプリプロセッサを使用して、移行中のトラブルを避けることはできます。ただし、理想的とはいえません。新しい多くの C++ 言語機能 (モジュールなど) の設計は、C++ のプログラミングにマクロを使用しなくても済むようになることを目指しています。モジュールが実際に C++ 17 に実装され、商用の実装として開発者が利用できるようになるまでは、少量のプリプロセッサをうまく活用すれば、animals ライブラリをヘッダーのみのライブラリやモジュールとしてビルドすることができます。animals.h ヘッダーの内部に、マクロを含まない ANIMALS_­EXPORT マクロを条件付きで定義し、モジュールとしてエクスポートすることを想定している名前空間の前に付けます (図 3 参照)。

図 3 ライブラリをヘッダーのみのライブラリやモジュールとしてビルド

C:\modules> type animals.h
#pragma once
#include <stdio.h>
#ifndef ANIMALS_EXPORT
#define ANIMALS_EXPORT
#endif
namespace animals { namespace impl {
// Please don't look here
}}
ANIMALS_EXPORT namespace animals {
// This is all yours
}

これで、開発者がモジュールに不慣れな場合や、モジュールが適切に実装されていない場合に、animals.h ヘッダーをインクルードして、他のヘッダーのみのライブラリと同様に使用できるようになります。ただし、次のようにモジュール インターフェイスを更新して ANIMALS_EXPORT を定義すれば、この同じヘッダーで一連のエクスポート宣言を生成できます。

C:\modules> type animals.ixx
module animals;
#define ANIMALS_EXPORT export
#include "animals.h"

最近の C++ 開発者の多くはマクロをあまり使用しません。しかし、マクロはライブラリをモジュールに移行するときに便利なテクニックです。何より優れている点は、animals.h ヘッダーをインクルードする側のアプリには無害なものとしてマクロが見えますが、単にモジュールをインポートする側のアプリにはマクロは見えません。マクロはモジュールのメタデータが作成される前に取り除かれるため、モジュールを利用する側のアプリや、他のライブラリ、モジュールにマクロが取り込まれることはありません。

モジュールは C++ 待望の追加機能なので、コンパイラ用に完全な商用サポートを備えた更新プログラムが提供されることを期待しています。今のところ、C++ のモジュール システムという可能性に向かって C++ の標準を推し進めているところですが、一緒に試してみませんか。モジュールについての理解を深めたい方は、技術仕様書 (goo.gl/Eyp6EB、英語) をお読みいただくか、昨年 CppCon で Gabriel Dos Reis が行った講演 (youtu.be/RwdQA0pGWa4、英語) をご覧ください。

コルーチン

コルーチン (旧称、再開可能な関数) が Visual C++ でサポートされるようになってしばらく経ちましたが、スタックベースの C 言語仕様に深く根付く真のコルーチンがこれからも C++ 言語でサポートされ続けることを期待しています。コルーチンを取り上げようと考えたとき、MSDN Magazine でこのトピックについてこれまでにも 4 本のコラムを書いていたことを思い出しました。その中で最も新しいのは 2015 年 10 月号のコラム (https://msdn.microsoft.com/ja-jp/magazine/mt573711) です。そこでは、Visual C++ 2015 から提供されるようになったコルーチンのサポートを取り上げています。今回はコルーチンのメリットを再発見するのではなく、もう少し細部に踏み込んでみることにします。C++ 17 でコルーチンが採用されるには課題が 1 つあります。それは、コルーチンが自動的な型の推論を行うことを標準化委員会が好ましく思っていないことです。コルーチンの型はコンパイラが推論し、開発者がその型について考える必要がないようにします。

auto get_number()
{
  await better_days {};
  return 123;
}

コンパイラは適切なコルーチンの型を生成できるだけでなく、関数の戻り値の型も推論できるとしていた C++ 14 の機能も継承しています。

auto get_number()
{
  return 123;
}

しかし、標準化委員会は、型の推論という考え方がコルーチンに拡張されることを快く思っていません。問題は、C++ 標準ライブラリが適切な候補を提供していないことです。最も適切だと思われる候補は扱いにくい std::future で、その実装は複雑になることが多く、あまり実用的でない設計になっています。また、コルーチンが生み出す非同期ストリームにはほとんど役立ちません。この非同期ストリームは、1 つの値を非同期に返すのではなく、複数の値を生み出します。したがって、コンパイラが 1 つの型を提供できず、C++ 標準ライブラリが適切な型を提供しないとしたら、コルーチンを使い続けるためには、実際のしくみを詳しく見ておく必要があります。次のような待機可能なダミーの型があるとします。

struct await_nothing
{
  bool await_ready() noexcept
  {
    return true;
  }
  void await_suspend(std::experimental::coroutine_handle<>) noexcept
  {}
  void await_resume() noexcept
  {}
};

何の処理も行いませんが、待機することによってコルーチンを構成することができます。

coroutine<void> hello_world()
{
  await await_nothing{};
  printf("hello world\n");
}

繰り返しになりますが、コンパイラがコルーチンの戻り値の型を自動的に推論することができず、std::future を使用しないことにしたら、このコルーチン クラス テンプレートをどのように定義すればよいでしょう。

template <typename T>
struct coroutine;

ここではあまり多くのことを取り上げるスペースがないので、何も返さないコルーチン (void) の例だけを見てみます。以下がその例です。

template <>
struct coroutine<void>
{
};

まず、コンパイラは、コルーチンの戻り値の型で promise_type を探します。特にコルーチンのサポートを既存のライブラリに組み込む必要がある場合など、これに対応する方法は他にもありますが、今回はコルーチン クラス テンプレートを作成しているため、単純に次のように宣言します。

template <>
struct coroutine<void>
{
  struct promise_type
  {
  };
};

次に、少なくとも値を返さないコルーチンの場合、コンパイラはコルーチンのプロミスで return_void 関数を探します。

struct promise_type
{
  void return_void()
  {}
};

return_void では何も処理する必要はありませんが、return_void は、コルーチンの論理的な結果を調査する準備ができたという状態変化のシグナルとして、さまざまな実装で使用されます。コンパイラは initial_suspend 関数と final_suspend 関数のペアも探します。

struct promise_type
{
  void return_void()
  {}
  bool initial_suspend()
  {
    return false;
  }
  bool final_suspend()
  {
    return true;
  }
};

コンパイラはこれらの関数を使用して、最初と最後のコードをコルーチンに挿入します。これにより、コルーチンを中断状態から開始するかどうかと、完了前にコルーチンを中断するかどうかをスケジューラーに通知します。この関数のペアは実際には待機可能な型を返すことができるため、コンパイラは次のように両方を待機できます。

coroutine<void> hello_world()
{
  coroutine<void>::promise_type & promise = ...;
  await promise.initial_suspend();
  await await_nothing{};
  printf("hello world\n");
  await promise.final_suspend();
}

待機するかどうかと、中断ポイントを挿入するかどうかは、実現しようとしていることによって変わります。特に、コルーチンの完了後にコルーチンにクエリする必要がある場合は、必ず最後の中断を用意します。最後の中断がないと、コルーチンはプロミスによって取得された値をクエリする機会を得る前に破棄されることになります。

コンパイラが次に探すのは、プロミスからコルーチン オブジェクトを取得する方法です。

struct promise_type
{
  // ...
  coroutine<void> get_return_object()
  {
    return ...
  }
};

コンパイラは promise_type がコルーチン フレームの一部として割当てられるようにします。次に、このプロミスからコルーチンの戻り値の型を生成する方法が必要です。これを呼び出し元に返すことになります。ここでは、coroutine_handle いうコンパイラが提供する非常に低レベルなヘルパー クラスを使用しなければなりません。このヘルパー関数は、現状 std::experimental 名前空間で提供されています。coroutine_handle はコルーチンの 1 回の呼び出しを表します。したがって、このハンドルをコルーチン クラス テンプレートのメンバーとして格納できます。

template <>
struct coroutine<void>
{
  // ...
  coroutine_handle<promise_type> handle { nullptr };
};

ここではハンドルを nullptr に初期化して、コルーチンが現在実行中ではないことを示していますが、コンストラクターを追加して、新しく構成されたコルーチンにハンドルを明示的に関連付けることもできます。

explicit coroutine(coroutine_handle<promise_type> coroutine) :
  handle(coroutine)
{}

コルーチン フレームはスタック フレームに少し似ていますが、動的に割り当てられるリソースで、破棄しなければならないので、自然の流れとしてそのためのデストラクターを用意します。

~coroutine()
{
  if (handle)
  {
    handle.destroy();
  }
}

コピー操作を削除して、移動セマンティクスも可能にしますが、考え方は同じです。ここで、コルーチン オブジェクトのファクトリとして機能する promise_type の get_return_object 関数を実装します。

struct promise_type
{
  // ...
  coroutine<void> get_return_object()
  {
    return coroutine<void>(
      coroutine_handle<promise_type>::from_promise(this));
  }
};

これで、コンパイラがコルーチンを生成し、動作させるようになります。ここで、コルーチンに続けてシンプルな main 関数を用意します。

coroutine<void> hello_world()
{
  await await_nothing{};
  printf("hello world\n");
}
int main()
{
  hello_world();
}

hello_world の結果を操作するわけではありませんが、このプログラムを実行すると、printf が呼び出され、見慣れたメッセージがコンソールに出力されます。これで、コルーチンは実際に完成したのでしょうか。その質問はコルーチンに問いかけます。

int main()
{
  coroutine<void> routine = hello_world();
  printf("done: %s\n", routine.handle.done() ? "yes" : "no");
}

今回は、コルーチンで何も行っていませんが、コルーチンが完了したかどうかを問い合わせています。結果は次のようになります。

hello world
done: yes

promise_type の initial_suspend 関数は false を返すので、コルーチン自体は処理を中断しません。また、await_nothing の await_ready 関数は true を返すため、中断ポイントを導入しません。最終結果は、実際には同期をとって完了するコルーチンになります。それ以外のことを行う理由はありません。すばらしい点は、コンパイラが同期をとって動作するコルーチンを最適化し、直列に実行するコードを非常に高速にするのと同じ最適化をすべて適用できる点です。しかし、このこと自体はそれほど興味深いことではありません。そこで、中断を追加します。つまり、少なくとも何らかの中断ポイントを追加します。何も行うことはありませんが、必ず中断するように、await_nothing 型を変えるだけです。

struct await_nothing
{
  bool await_ready() noexcept
  {
    return false;
  }
  // ...
};

この場合、コンパイラはこの待機可能なオブジェクトが準備できていないことを確認し、再開前に呼び出し元に戻ります。ここで先ほどのシンプルな hello world アプリに戻ります。

int main()
{
  hello_world();
}

このプログラムが何も出力しないことがわかって落胆するでしょう。その理由は明らかだと思いますが、コルーチンは printf を呼び出す前に中断したのに、コルーチン オブジェクトを所有する呼び出し元がコルーチンに再開する機会を提供していません。当然、コルーチンの再開は、ハンドルによって提供される resume 関数の呼び出すだけです。

int main()
{
  coroutine<void> routine = hello_world();
  routine.handle.resume();
}

hello_world 関数は printf を呼び出さずに戻りますが、resume 関数によってコルーチンが完了まで実行されるようになります。さらに詳しく説明するため、次のように再開の前後で handle の done メソッドを使用します。

int main()
{
  coroutine<void> routine = hello_world();
  printf("done: %s\n", routine.handle.done() ? "yes" : "no");
  routine.handle.resume();
  printf("done: %s\n", routine.handle.done() ? "yes" : "no");
}

結果には、呼び出し元とコルーチンとの対話が明確に示されます。

done: no
hello world
done: yes

次のように、軽量な協調的マルチタスク システムを非常に簡単に記述できるので、特に洗練された OS スケジューラーやスレッドが不足している組み込みシステムでは、これが非常に便利です。

while (!routine.handle.done())
{
  routine.handle.resume();
  // Do other interesting work ...
}

コルーチンは魔法ではありません。また、コルーチンを動作させるために、複雑なスケジューリングや同期ロジックは必要ありません。戻り値の型をがあるコルーチンをサポートするには、promise_type の return_void 関数を、値を受け取ってプロミスの内部に格納する return_value 関数に置き換えます。そうすれば、呼び出し元は、コルーチンの完了時に値を取得できます。一連の値を生成するコルーチンでは、promise_type に同様の yield_value 関数が必要ですが、その他は基本的に同じです。コンパイラが提供するコルーチン用のフックは、きわめて単純ですが、驚くほど柔軟です。この簡単な概要では表面をなぞった程度にしか説明できませんでしたが、この驚くような新しい言語機能の有用性が伝わればさいわいです。

Gor Nishanov (マイクロソフト C++ チームの開発者) は、最終的な標準化に向けてコルーチンを引き続き後押ししています。彼は、コルーチンのサポートを Clang コンパイラに追加することにも取り組んでいます。コルーチンの詳細については、技術仕様書 (goo.gl/9UDeoa、英語) をお読みいただくか、昨年 CppCon で Nishanov が行った講演 (youtu.be/_fu0gx-xseY、英語) をご覧ください。また、James McNellis は、Meeting C++ でコルーチンについての講演 (youtu.be/YYtzQ355_Co) を行いました。

マイクロソフトでは C++ に関して、他にもさまざまなことが行われています。新しい C++ 言語機能 (C++ 14 の変数テンプレートなど) を追加して、変数のファミリーを定義できるようにしています (goo.gl/1LbDJ2、英語)。Neil MacIntosh は、C++ 標準ライブリに文字列とシーケンスのバインドセーフ ビューについての新しい提案に取り組んでいます。goo.gl/zS2Kau (英語) および goo.gl/4w6ayn (英語) で span<> と string_span について調べることができます。また、どちらも利用可能な実装があります (GitHub.com/Microsoft/GSL、英語)。

最近、バックエンドでは、リテラル文字列を使用して呼び出したときに、C++ オプティマイザが strlen と wcslen への呼び出しを最適化することに関して、考えていたよりもはるかに効率がよいことがわかりました。それが厳重に守られた秘密であるとしても、特に新しいことではありません。新しいことと言えば、Visual C++ はついに、10 年以上不足していた完全に空ベースの最適化を実装しました。__declspec(empty_bases) をクラスに適用すると、直接的なすべての空ベースのクラスがオフセット 0 でレイアウトされることになります。このような大きな変更を導入するにはコンパイラへのメジャー バージョンの更新プログラムが必要です。また、古いレイアウトを想定する C++ 標準ライブラリ型がまだ存在します。そのため、既定では実装されません。ライブラリの開発者は最終的には最適化を利用できるようになります。Windows ランタイム用の Modern C++ (moderncpp.com、英語) は、特にこの最適化からメリットを得ていて、この機能がコンパイラに追加された実際の理由となっています。2015 年 12 月号のコラムで取り上げたように、私がマイクロソフトの Windows チームに参加し、moderncpp.com (英語) に基づいて Windows ランタイム用の新しい言語プロジェクションを構築したのは最近のことです。また、これはマイクロソフトで C++ を推し進める助けとなっています。誤解しないでいただきたいのですが、マイクロソフトの C++ に関する取り組みは真剣です。


Kenny Kerr は、マイクロソフトの Windows チームに所属するソフトウェア エンジニアです。彼のブログは kennykerr.ca (英語) で、Twitter は @kennykerr (英語) でフォローできます。

この記事のレビューに協力してくれたマイクロソフト技術スタッフの Andrew Pardoe に心より感謝いたします。