C++1z 構造化束縛

C++1zから、タプルやクラスを分解する「構造化束縛 (Structured Binding)」という機能が入ります。他の言語では多重代入のように呼ばれているもので、C++11標準ライブラリのstd::tie()関数の代わりとして使用できます。

以下は、タプルを返す関数の戻り値を、分解して受け取るコードです:

tuple<int, string> f();

// aにはintの値、bにはstringの値が代入される
auto [a, b] = f();

構造化束縛には、autoプレースホルダーを型として指定したあと、角カッコ [ ] 内に、分解した値を受け取る変数名のリストを記述します。各変数に対して個別に具体的な型を指定することはできません。

タプルの場合は、std::tuple_size<T>::valueという式が妥当な型が対象となり、タプルのインタフェースget<I>(t)std::tuple_element<I,T>::typeが使用できる型であれば、std::tupleと同様に構造化束縛で分解できます(ADLで呼び出すため、getにはstd::が付いていない)。標準ライブラリでは、std::pairstd::arrayもタプルとして分解できます。

これを使用して、map::insert()の戻り値を簡単に受け取れたりしますね。

auto [iter, success] = m.insert(x);
// iterは、挿入された要素を指すイテレータ
// successは、挿入に成功したかを表すbool値 (重複時に失敗する)

クラスの分解

構造化束縛はタプルだけでなく、クラスも分解できます。クラスは、非静的publicメンバ変数が列挙されます(public基本クラスのものを含む)。そのクラスは、無名共用体メンバを持ってはいけません。

struct Point { int x, y; };
Point f();

// xにはPoint::xの値、yにはPoint::yの値が代入される
const auto [x, y] = f();

メンバ変数は、ビットフィールドになっていても分解できます。

struct X {
    int flag : 1;
    int counter : 15;
};

X f();
const auto [flag, counter] = f();

組み込み配列の分解

組み込み配列を分解することもできます。その際、受け取る変数名のリストは、配列の要素数と同じ個数だけ指定する必要があります。要素数が一致しない場合はコンパイルエラーになります:

// 配列の各要素を取得。
// 受け取る個数が配列の要素数と一致していなければならない
int ar[] = {3, 1, 4};
auto [a, b, c] = ar;

参照修飾

変数を参照として受け取りたい場合は、autoのあとに参照の修飾をします。以下は、範囲for文でstd::mapオブジェクトの要素をキーと値に分解するコードです:

std::map<int, string> m;

for (const auto& [key, value] : m) {}

属性の指定

属性は、分解した個別の変数に対しては指定できず、全体に対して指定することになります:

[[maybe_unused]] auto [a, b] = f();

参照

お断り

この記事の内容は、C++1zが正式リリースされる際には変更される可能性があります。正式リリース後には、C++日本語リファレンスサイトcpprefjpの以下の階層の下に解説ページを用意する予定です。

新装版 達人プログラマー

書くのが遅くなってしまい、発売からけっこう経ってしまいましたが、オーム社さんから『新装版 達人プログラマー』をいただきました。ありがとうございます。

f:id:faith_and_brave:20161117174934j:plain:w480

本書は、ピアソン・エデュケーションから出版されていた『達人プログラマー』を、オーム社が内容を新たに再出版したものとなっています。

改定内容は、オーム社のブログで紹介されています。

本書は、原書が出たのが1999年、日本語版が出たのが2000年となっており、かなりの時間が経過しています。今回の改定で、本書内で紹介されているツールに最近のものが追加されていたりとうれしい変更が多々入っていますが、その変更がたとえ入っていなかったとしても、ソフトウェア開発の手法、プログラマとしての取り組み方として大事なものが2016年のいまでも変わらないものが多くあることを再認識できました。

このようなソフトウェア開発とプログラマとしての取り組み方を包括的に解説した書籍として、本書『達人プログラマー』の代わりになるものは私が把握している限りではありません。書籍によって目標とするものが違うため、『達人プログラマー』とは異なるアプローチで学べる本はいくつかありますが(この記事の最後の方にまとめます)、本書を読む価値はいまだ下がることはありません。

私が行っているプログラマへの教育の仕事でも、教科書として本書の旧版を使用していました。わりと最近のことです。それだけに、新装版が出版されて、本書がまた手に入りやすくなったのはとてもうれしいことだと思います。

プログラマ、開発者として成長したい方、またはその教育者の方には、2016年のいまでもぜひ読んでいただきたいです。

他の書籍

『達人プログラマー』とは異なるアプローチですが、プログラマとしての取り組み方として参考になる書籍をいくつか挙げます。

これらの書籍は、ソフトウェア開発全般や技法を学ぶというよりは、プログラマとしての取り組み方、成長の仕方を主眼にしています。私はプログラマに最も大事なものは「生涯学習を身につけること」だと考えているのですが、これらの書籍ではそういったことがよく学べるでしょう。

C++1z 単一要素の波カッコ初期化をTに推論する

単一要素の波カッコ初期化(braced init list)を、(代入構文ではなく)直接autoで受けた場合のルールが、以下のように変更になります:

auto a {1};      // C++14まではinitializer_list<int>
                 // C++1zではint

auto b {1, 2};   // C++14まではinitializer_list<int>
                 // C++1zではコンパイルエラー

auto c = {1};    // これまで通りinitializer_list<int>
auto d = {1, 2}; // これもinitializer_list<int>

参照

お断り

この記事の内容は、C++1zが正式リリースされる際には変更される可能性があります。正式リリース後には、C++日本語リファレンスサイトcpprefjpの以下の階層の下に解説ページを用意する予定です。

C++1z if文とswitch文の条件式と初期化を分離

for文が for (初期化式; 条件式; 加算式)になっているのと同様に、if文とswitch文にも、条件式の前準備としての初期化式をif (初期化式; 条件式)のように書けるようになります。

// mapへの要素挿入
if (auto p = m.try_emplace(key, value); !p.second) {
  // 要素挿入に失敗 (すでにkeyが登録されている)
}
else {
  // 挿入した要素を使用して何か処理する
  process(p.first);
}
if (status_code c = open_file(); c != SUCCESS) {
  // 処理に失敗
  // 失敗の原因ごとに処理をする
  if (c == NO_FILE) {
  }
  if (c == FORMAT_ERROR) {
  }
}
switch (Button button(args); Status s = button.status()) {
  case OK: break;
  default: throw BadStatus(toString(s));
}

構文としては、

if (condition)
if (init-statement; condition)

のように2種類用意されるのではなく、初期化式を省略可能にして、以下のように定義されます。

if (init-statement;(opt) condition)

そのため、後に紹介するif constexpr文でも初期化式は使用できます。

参照

お断り

この記事の内容は、C++1zが正式リリースされる際には変更される可能性があります。正式リリース後には、C++日本語リファレンスサイトcpprefjpの以下の階層の下に解説ページを用意する予定です。

C++1z インライン変数

C++14まで、関数に対してインライン化の指定ができましたが、C++1zでは変数にもインライン指定ができるようになります。

変数に対してインライン指定をすると、翻訳単位を跨いでひとつのオブジェクトになります。これによって、ヘッダファイルで変数を定義できるようになります。

// foo.h
#ifndef FOO_HEADER
#define FOO_HEADER

struct Foo {};

struct X {
    // クラス定義の外でfooを定義する必要がない
    static inline Foo foo;
};

// これまでは、ヘッダファイルで以下のように変数を宣言し、
// foo.h
// struct X {
//   static Foo foo;
// };
//
// ソースファイルで変数を定義する必要があった
// foo.cpp
// Foo X::foo;

#endif
// foo.h
#ifndef FOO_HEADER
#define FOO_HEADER

struct Foo {};

// グローバル変数の定義をヘッダに書ける。
// 複数の翻訳単位を跨いでひとつのオブジェクトになる
inline Foo theFooObject;

#endif

なお、constexpr静的メンバ変数は自動的にインラインになりますので、明示的にinlineを指定する必要はありません。

参照

修正履歴

  • 2016/11/12 21:55 コメント欄での指摘を受け、「constexpr変数は自動的にインラインになる」としていた文章を、「constexpr静的メンバ変数は自動的にインラインになる」と修正。

お断り

この記事の内容は、C++1zが正式リリースされる際には変更される可能性があります。正式リリース後には、C++日本語リファレンスサイトcpprefjpの以下の階層の下に解説ページを用意する予定です。

C++1z 非推奨だったbool型に対するインクリメント演算子を削除

bool型に対してインクリメントすると固定でtrueになる仕様がありましたが、この仕様はC++98時点で非推奨になっていました。C++1zではこの機能が削除されます。

以下のようなコードは、C++1zではコンパイルが通らなくなりますので注意してください。

#include <cassert>

int main()
{
    bool b1 = false;
    bool b2 = true;

    // C++98から非推奨だがC++14まで合法、
    // C++1zではコンパイルエラー
    ++b1;
    b2++;

    assert(b1 == true);
    assert(b2 == true);
}

ちなみに、bool型に対するデクリメントは元々できません。

仕様としては、「算術型 (arithmetic type) に対してインクリメントできる」と書かれていたものが「(CV修飾された) bool型以外の算術型に対してインクリメントできる」に変わります。

参照

お断り

この記事の内容は、C++1zが正式リリースされる際には変更される可能性があります。正式リリース後には、C++日本語リファレンスサイトcpprefjpの以下の階層の下に解説ページを用意する予定です。

C++1z 非推奨だったregisterキーワードを削除

C++11から非推奨となっていたregisterキーワードが、C++1zで削除されます。

ただし、C++11のautoのように、registerキーワードを将来の標準でほかの用途に再利用することを考慮して、予約語としては残ります。

C++1zでは、register記憶クラス指定子を使用したプログラムは、コンパイルできなくなりますので注意してください。

参照

お断り

この記事の内容は、C++1zが正式リリースされる際には変更される可能性があります。正式リリース後には、C++日本語リファレンスサイトcpprefjpの以下の階層の下に解説ページを用意する予定です。

Elixirでコンソールに出力する文字色を変更して戻す

# 緑(green) + 太字(bright)で"hello"を出力し、元の文字色に戻す(reset)
IO.puts IO.ANSI.format([IO.ANSI.bright <> IO.ANSI.green, "hello", IO.ANSI.reset])

# 元の文字色で"world"が出力される
IO.puts "world"

リリースマネージャのDistillerymix releaseしたあとにコンソールの文字色が戻らないバグがあって、調べて直してpull requestを送ったので、そのときに書いたミニマムコードです。

C++1z 全ての非型テンプレート引数の定数式評価を許可

C++1zでは、非型テンプレート引数(non-type template argument)で扱える型はとくに変わりませんが、渡せる値についての制限緩和が行われます。

今回緩和されるのは、ポインタの値です。C++14までは、以下のような制限がありました:

  • 静的記憶域を持つ完全オブジェクトへのポインタ値もしくは参照、もしくは
  • ヌルポインタ値に評価される定数式、
  • ヌルメンバポインタ値に評価される定数式であること

つまり、staticでないオブジェクトへのポインタは、ヌルポインタしか渡せなかったのです。

C++1zではこの制限が撤廃され、定数式で評価されるポインタならなんでも渡せるようになります。その許可される定数式での評価には、配列からポインタへの変換や、関数から関数ポインタへの変換、修飾の変換なども含まれます。

struct A {};

template <const A* p>
struct X {};

constexpr A a{};
constexpr A ar[3] = {};

constexpr const A* get_pointer() { return &a; }
constexpr const A* get_array_pointer() { return ar; }

int main()
{
    X<nullptr> {};             // OK : これまで許可されていた
    X<get_pointer()> {};       // OK : C++1z
    X<get_array_pointer()> {}; // OK : C++1z
}

参照

お断り

この記事の内容は、C++1zが正式リリースされる際には変更される可能性があります。正式リリース後には、C++日本語リファレンスサイトcpprefjpの以下の階層の下に解説ページを用意する予定です。

C++1z 非型テンプレートパラメータのauto宣言

C++14まで、以下のように書いていた「指定された型の定数を受け取る」意図の非型テンプレートパラメータ(non-type template parameter)ですが、

template <class T, T V>
struct X;

X<int, 3>;

C++1zではこの用途のためのシンタックスシュガー(糖衣構文、syntactic sugar)が導入されます。そのためには、テンプレートパラメータをautoにして値を受け取るようにします。

template <auto X>
struct A {};

A<3>;    // OK
A<true>; // OK
A<'a'>;  // OK
A<3.14>; // コンパイルエラー (浮動小数点数は渡せない)

テンプレートの中では、decltypeを使用すればXの型を取得できます。

このautoは、変数宣言のautoと同じくプレースホルダーという扱いになります。そのため、template <auto* P>template <auto& R>のような推論補助もできます。

#include <type_traits>

template <auto* X>
struct A {
    using type = decltype(X);
};

int main()
{
    constexpr int* p = nullptr;
    static_assert(std::is_same<A<p>::type, int*>{});
}

参照

お断り

この記事の内容は、C++1zが正式リリースされる際には変更される可能性があります。正式リリース後には、C++日本語リファレンスサイトcpprefjpの以下の階層の下に解説ページを用意する予定です。