Edited at

久々なのでC++17の情報を集めてみる

More than 3 years have passed since last update.

なんか超テキトーに書いて投稿した初稿がそこそこStockされてて、もっとちゃんと書けやゴルァって怒られそうな気がするので更新します。

オヒサシブリデス。

最近C++の最新情報を集めるのもサボってまして、本の虫で最新の論文を紹介している記事も流し読み状態。これではいけない。今や2016年、順当に行けばC++1zがC++17になる日も近いではないか。

というわけで、まずはStandard C++の公式サイトを見てみる。conceptはよ。networkingはよ。

Current Status - Stancard C++


Recent milestones: C++17 nearly feature-complete, second round of TSes now under development


ふむふむ。そろそろFCで、TSが現在策定中であると。なるほどなるほど。で、何が標準入りするの?

コア言語機能


  • fold式

  • メッセージ無しのstatic_assert

  • ネストされた名前空間の宣言

  • u8文字リテラル

  • 16進浮動小数点リテラル

  • registerの削除

  • トライグラフの削除

  • constexprラムダ式

  • ラムダ式で*thisのキャプチャ

  • [[fallthrough]], [[nodiscard]], [[maybe_unused]]

  • 不明なattributeの無視

  • typenameでテンプレートテンプレートパラメータ

  • 異なる型を返すbegin/endでのrange-based-for

ライブラリ


  • Special Math

  • File System

  • Library Fundamentals

  • Parallelism

( ゚д゚)< conceptはよ。networkingはよ。

( ゚д゚)< ……

( ゚д゚ )

知ってた。

泣いてなんかない。

まあ、とりあえず標準入り予定の奴らを見ていこうと思います。


コア言語機能


fold式

再帰を使わずともパラメータパックを展開できるようになりました。

そうね。再帰は分かりづらいし。

ただ、fold式も慣れないとなんだかむず痒い書き方だと思います。

fold式には以下のような書き方があります。


c++1z

( pack op ... );

( ... op pack );
( pack op ... op expr );
( expr op ... op pack );

opは演算子で、+ - * / % ^ & | = < > << >> += -= \*= /= %= ^= &= |= <<= >>= == != <= >= && || , .* ->*のいずれかが入ります。

packはパラメータパックを含む式、exprはパラメータパックを含まない式です。

fold式には括弧は必須です。括弧をつけないとエラーになります


c++1z

(... + args); //OK

... + args; //ill-formed

fold式にはパラメータパック識別子が右に来るものと左に来るものがありますが、展開の順序が違います。

例えば、argsという名前のパラメータパックがあって、4個の引数が渡されたとき、仮にarg1, arg2, arg3, arg4に展開されるとしましょう。

上記の4種類の書き方はそれぞれ、以下のように展開されます。


c++1z


(args + ...);
// arg1 + (arg2 + (arg3 + arg4));

(... + args);
// ((arg1 + arg2) + arg3) + arg4;

(args + ... + 42);
// arg1 + (arg2 + (arg3 + (arg4 + 42)));

(42 + ... + args);
// (((42 + arg1) + arg2) + arg3) + arg4;


さて、fold式を使うとどんなことができるのか。例えば、C++14までは、


c++14

int sum() {

return 0;
}

template<typename T>
auto sum(T&& t) {
return t;
}

template<typename T, typename ...Args>
auto sum(T&& t, Args&& ...args) {
return t + sum(args ...);
}


みたいなことをやらなきゃいけなかったのが、


c++1z

template<typename ...Args>

auto sum(Args&& ...args) {
return (... + args);
}

と書けます。

また、パラメータパックは展開する時にパラメータパックを含んだ式にすることもできるので、


c++1z

template<typename ...Args>

void print(Args&& ...args) {
(..., (std::cout << args));
}

などとすることができます。

もっともこの場合、


c++1z

template<typename ...Args>

void print(Args&& ...args) {
(std::cout << ... << args);
}

と書いても大体同じです1


メッセージ無しのstatic_assert

C++14までは


c++14

static_assert(条件式, "メッセージ");


と、アサーションをしたいけど特に書くべきメッセージが思いつかなくても、必ずメッセージを書く必要がありました。

C++17でメッセージはオプションになります。


c++1z

static_assert(条件式); // well-formed


小粒の修正ですがありがたいです。


ネストされた名前空間の宣言

ネストされた名前空間を一度に宣言できます。

C++14までは


c++14

namespace my {

namespace name {
namespace space {
}
}
}

だったのが、


c++17

namespace my::name::space {

}

と書けるようになります。

これも便利ですね。


u8文字リテラル

u8プレフィックスが文字リテラルにも使えるようになります。

使えるようになりますが、u8文字リテラルで書けるのはutf-8エンコードでchar1文字に収まる文字だけです。


c++1z

char a = u8'a'; // well-formed

char hiragana_a = u8'あ'; // ill-formed

つまりほぼASCII文字コードのみです。


16進浮動小数点リテラル

浮動小数点リテラルも16進数で書けるようになりました。

整数リテラルの場合と同じように先頭を0xまたは0Xにして、2を底にした指数を必ずつける必要があります。


c++1z;

static_assert(0xA.8p0 == 10.5);

static_assert(0x.4p1 == 0.5);
static_assert(0x3.p-1 == 1.5);

上記の例のように、pの前に仮数部、pの後ろに指数を書き、実際の数は仮数×(2^指数)となります。

ぼく「0xで始まってて途中に.があれば16進浮動小数点数だって分かるんだから、p0は省略しても良くない?」

Bjarne先生「ダメです」

ダメらしいです2

何でダメなんだろう。10進浮動小数点表記でe0は省略してもいいのに。


registerの削除

registerキーワードはもはや過去の遺物です。今時のコンパイラはレジスタ渡しできる場合は最適化で勝手にレジスタを使ってくれるので、わざわざ指定する必要もありませんし。

似たような立場のautoキーワードは別の役割を与えられて復活しましたが、registerはC++標準のキーワードではなくなります。

逆に言えば、これから先はregisterという文字列をどこでも使えるようになったということでもあります。

registerキーワードがunregisterされた、とさっきから言いたくて仕方ない


トライグラフの削除

トライグラフはもはや過去の遺物です

……というかですね、トライグラフ見たことないです。

トライグラフについて知らないという方はこちら

今更こんな文字コードにこだわる人なんていませんし、というかむしろこれ、正規表現とかワイルドカード書こうとしてバグになり得る要素ですよね。

という訳で削除されます。削除されますが、多分誰も影響を受けないでしょう。


constexprラムダ式

ラムダ式をconstexprにすることができるようになります。

岡山の陶芸家の中3女子が喜びそうな変更ですね。まあ私もconstexpr教徒なので結構嬉しいですが。

書き方は、mutableを書くのと同じ場所にconstexprを書くだけ。引数リストの後ろ、戻り値型の前です。


c++1z

auto constexpr fn = []() constexpr { return 0; };

auto constexpr a = fn();

constexprなラムダ式はそれそのものがconstexprな値として扱われ、また、戻り値もconstexprになります。


ラムダ式で*thisのキャプチャ

C++14までのラムダ式では、thisをラムダ式が定義された文脈におけるオブジェクトの参照としてキャプチャすることしかできませんでした。

なので、ラムダ式が実行された時にthisの指すインスタンスが削除されている可能性がありました。

C++17では*thisをキャプチャすることができるようになり、ラムダ式はthisの指すオブジェクトをコピーキャプチャすることができるようになりました。


c++1z

#include <iostream>


class X {
int* a;
public:
X(int i):a(new int(i)) {}
X(const X& x): a(new int(*x.a)) {}
~X() { delete a; a = nullptr; }
auto get_fn() { return [*this]() { return *a; }; }
};

int main() {
auto fn = X(42).get_fn();
std::cout << fn() << std::endl;
}


まあ、あんまりいい例ではない気もしますが、上記の例ではfnが*thisをキャプチャしていれば42が出力されますが、thisをキャプチャすると実行時エラーが発生するかと思われます。

なお、thisと*thisはどちらか一方しかキャプチャできません。したがって、次のような書き方は不可能です。


c++17

auto fn = [this, *this] () { }; // ill-formed



[[fallthrough]], [[nodiscard]], [[maybe_unused]]

新たに追加されたattribute三種。


[[fallthrough]]

switch文でbreakせずに次のcaseラベルもしくはdefaultまで進む場合、breakなしが意図したものであることをコンパイラに伝えるためのattributeです。


c++1z

int a = 0;

switch(a) {
case 0:
std::cout << 0 << std::endl;
[[fallthrough]]; //これが無いとコンパイラは警告を出すかもしれない
case 1:
std::cout << 1 << std::endl;
}

なんてクソみたいなコードだ!

……それはともかく、こんな感じで[[fallthrough]]を書いておけばコンパイラの警告を抑制できるよ、ということですね。

そもそもswitchはあまり多用すべきではないと思っています。


[[nodiscard]]

[[fallthrough]]が警告を抑制するものだったのに対し、[[nodiscard]]は警告を出すための属性です。

意味は「無視すんなコラ!」ってところです。

[[nodiscard]]を付けて関数を宣言すると、ユーザーがその関数の戻り値を無視した場合に警告が出ます。


c++1z

struct error_status { };

[[nodiscard]] error_status fn() { return {}; }

int main() {
fn(); // warning
}


こうすることで、ユーザーが戻り値を処理せざるを得なくしています。

え、(void)付けちゃえば無視できる? うん、まあ、いくら何やっても無視する人は無視するよね……。


[[maybe_unused]]

宣言された変数や仮引数などが使用されていないと、コンパイラは警告を出します。

しかし、例えばデバッグビルドではデバッグ情報を出力するために使用されていた変数が、リリースビルドでは不使用になる、なんてことは珍しくありません。

そういった場合に、いちいち#ifdef DEBUGなんて書くのはバカバカしい。同じコードで警告さえ出なければいいのだから。

そこで[[maybe_unused]]です。

[[maybe_unused]]が付けて宣言された変数などは、使用されなくても警告が出ません。


不明なattributeの無視

attributeは[[]]で囲まれた文字列ですが、将来的に追加される可能性や、コンパイラが独自のattributeを追加する余地があります。

したがって、コンパイラは自分の知らないattributeを解析することがあるでしょう。そうした場合、全て無視することが規格で定められました。


typenameでテンプレートテンプレートパラメータ

テンプレートテンプレートパラメータで今までclassしか使えなかった場所で、typenameを使えるようになりました。


c++1z

template<template<...> typename T>

class X {};

意味は今までのclassキーワードの場合と同じです。


異なる型を返すbegin/endでのrange-based-for

C++14までは、begin/endで返すイテレータの型は同じでなければなりませんでした。

例えば、begin()がstd::string::iteratorを返すなら、endもstd::string::iteratorを返さなければなりませんでした。

しかし、C++17ではイテレータの型が異なっていても良いことになりました。


c++1z

#include <iostream>


struct X{
};

class iterator {
int i = 0;
public:
int operator *() const { return i; }
bool operator !=(int j) { return i != j; }
iterator& operator++() { ++i; return *this; }
};

iterator begin(const X&) { return {}; };
int end(const X&) { return 42; }

int main() {
for(auto&& i : X())
std::cout << i << " ";
}


と、こんな事もできるようになります。


ライブラリ


Special Math

元々boost.mathで実装されてた特殊関数の標準実装です。

以下の関数が含まれます。


  • ベータ関数

  • 第1種ベッセル関数

  • 第2種ベッセル関数(ノイマン関数)

  • 第1種変形ベッセル関数

  • 第2種変形ベッセル関数

  • 第1種完全楕円積分

  • 第2種完全楕円積分

  • 第3種完全楕円積分

  • 第1種不完全楕円積分

  • 第2種不完全楕円積分

  • 第3種不完全楕円積分

  • 指数積分

  • エルミート多項式

  • ルジャンドル多項式

  • ルジャンドルの陪多項式

  • ラゲールの多項式

  • ラゲールの陪多項式

  • リーマン・ゼータ関数

  • 第1種球ベッセル関数

  • 第2種球ベッセル関数(球ノイマン関数)

  • ルジャンドルの球陪多項式

( ゚д゚)< すまない、数学者と物理学者以外は帰ってくれないか

さっぱり分かりません。訳が合ってるかどうかも不確かです。

とりあえず出てきた関数の名前をググッてWikipediaの記事あたりから日本語の名前見つけて並べておきました。

リーマン・ゼータ関数は聞いたことだけはあります。あれでしょ、ミレニアム懸賞問題になってるリーマン予想のやつ。

えーっと、多分ガチな物理や数学の研究者じゃないと使わないと思うので、そういう人が解説やってくれることを願っておきます。私には無理です。


File System

苦節十年以上の時を経て、ファイルシステムが標準入りです。

というか、標準でファイルシステムを扱う方法が今までなかったってのがある意味すごい。

あれですね。Windowsとそれ以外のOSのパスの違いとか、ファイルシステムで使える文字の違いとか、多分そういうのが足枷になったんだと思います。

で、どういう物が入っているのかというと、


path

ファイルパスを統一的に扱えるクラス。もうこれで怖くない……といいなあ


filesystem_error

ファイルシステムで何かエラーがあったら投げられてきます。


directory_entry

ディレクトリエントリ、つまりファイルもしくはディレクトリを表すクラス。


directory_iterator

ディレクトリの中身をたどるイテレータ。フリー関数でbegin/endが定義されてるので、for文で回せます。つまりディレクトリの中身を列挙できます。


recursive_directory_iterator

directory_iteratorの再帰版。つまりサブディレクトリの中まで全部列挙します。


file_status

ファイルの種類とかパーミッションとか。directory_entry::statusとかで取得できます。


space_info

ファイルシステムの空き領域

などなど。

まあ、特に目新しい物ではありません。パスを扱うのに何使うか悩まなくて良くなるよ! ってだけです。


Library Fundamentals

C++11で標準入りを逃した連中が帰ってきます。


optional

みんな大好きoptional。ついに標準入りでございます。えーっと、説明は、いいよね?


any

みんな大好きany(ry


basic_string_view

basic_stringに対するビューを扱います。つまり、文字列に変更を加えない操作をより簡単に行うことができます。

例えば、文字列の一部を取り出したい時、substrを使ってもメモリを新たに確保することはありません。

あくまでもビューなので、元のbasic_stringのインスタンスが破棄されたら、もうアクセスはできません。

など。

あとはshared_ptrで配列をサポートしたり、サンプリングと検索に関するアルゴリズムが追加されたり、型消去のメモリアロケーションに関するあれこれが追加されたり、タプルを関数の引数に展開できるようになったりと、まあそんな感じらしいです。


Parallelism

並列処理ライブラリ。

前々から来るぞ来るぞと言われておりましたが、不勉強でして、まだどういうことができるのか分かってません。調べたら後で追記しようと思います眠い

と、まあ、こんな感じらしいですよ奥さん。

随分駆け足で見てみたので、情報として不正確な部分も多そうですし、多分調べきれてないところもあります。ツッコミもください。サンプルコードも入れたほうがいいと思うし。

でもそろそろみんなC++17の記事書いてもいいと思うんだ。まあ主要コンパイラでまだ実装されてない機能とかは難しいですが。

てなわけで、今回はこの辺で。

conceptはよ。networkingはよ。はよ。





  1. 前者の場合は(((std::cout << arg1), std::cout << arg2), std::cout << arg3), ...と展開されて、後者の場合は(((std::cout << arg1) << arg2) << arg3) << ...と展開されるので、いつも同じとは限りません。 



  2. この会話は捏造です。