本の虫

著者:江添亮
ブログ: http://cpplover.blogspot.jp/
メール: boostcpp@gmail.com
Twitter: https://twitter.com/EzoeRyou
GitHub: https://github.com/EzoeRyou

アマゾンの江添のほしい物リストを著者に送るとブログ記事のネタになる

筆者にブログのネタになる品物を直接送りたい場合、住所をメールで質問してください。

C++標準化委員会の文書: P0340R0-P0349R0

P0340R0: Making std::underlying_type SFINAE-friendly

underlying_typeに対して非enum型を渡した時にクラスが定義されるように変更することで、SFINAEフレンドリーにする提案。

入るべきだ。

P0341R0: Leveraging parameter packs outside of templates

この提案は素晴らしい。

テンプレートパラメーターパックは素晴らしい。しかし、その利用はテンプレートに制限されている。実にもったいないことだ。パラメーターパックはもっと使われるべきだ。

そもそもパラメーターパックを、そのまま変数にしたい。

template < typename ... T >
struct X
{
    T ... t ;// 認められるべきだ。
} ;

template < typename ... T >
void f( T ... a )
{
    T ... fa(f(a)) ; // 認められるべきだ
}

パラメーターパックはテンプレートの外からでもアクセスできるべきだ。例えばi番目パラメーターパックの値を得るために、以下のようなコードが書けるべきだ。

template<typename ...T>
struct easy_tuple {
	template<typename U> easy_tuple(U &&u) : t{u}... {}
	T ...t;  // パラメーターパックをそれぞれデータメンバーとして持つ
};

template <int i, typename ...T> auto ith_argument(T && ...t) { /*i番目のパラメーターパックを返すコード*/ }
	
template<typename int i; typename ...T> auto get(easy_tuple<T...> tup) {
	return ith_argument<i>(tup....t& ...);  クラスの外側からパラメーターパックのデータメンバーアクセス
}

さて、これはほんの基本だ。本題は、パラメーターパックはテンプレートの外でも扱えるようになるべきだ。そのために、パックリテラルを追加する。

template<typename...T = <double, double> >
struct euclidean_space { /* ... */ };

パックリテラルがあるからには、パック値が存在する。例えば、以下のようなコードは、現在ill-formedである。

auto f()
{
    return { 2, "foo"} ;
}

{}で囲まれた式は何らかのリテラルのように見えるが、現在はそのような扱いを受けていない。パックリテラルが導入されれば、braced-init-listは第一級式として扱えるようになる。すなわち、型は<int, char const *>というパックとなる。z

パックを返すのは、pairやtupleで包んで返すより自然だ。

<double, double> calculateTargetCoordinates();
double distanceFromMe(double x, double y);
	
void launch() {
	if(distanceFromMe(calculateTargetCoordinates()...))
		getOuttaHere();
}

名前付きパックリテラル

<string topSong, person president, double avgTemp> someFactsAboutYear(int year) {
  if(year==1962)
    return {"Stranger On The Shore", Presidents.get("Kennedy"), 14};
}

パックリテラルはtupleよりも直感的な型リストを実現できる。

template<typename L, typename R> struct append;
	
template<typename ...Ls, typename ...Rs>
struct append< <Ls...>, <Rs...> > {
	using type = <Ls..., Rs...>;
};

パラメーターパックは型だけではなく非型も入れられるので、値リストも作れる。

template<typename theoretical, typename actual> struct accuracy;

template<double... predictions, double... measurements> 
struct accuracy< <predictions...>, <measurements...> >) { /* ... */ };

この提案は素晴らしい。パラメーターパックが第一級市民になる。夢が広がる。ぜひとも入るべきだ。

P0342R0: Timing barriers

パフォーマンスの計測は高速なコードを書くために重要だ。パフォーマンスを計測するには処理の前後で時間を取得して差分を得る。

int main()
{
    auto start = std::chrono::high_resolution_clock::now() ;
    do_something() ;
    auto end = std::chrono::high_resolution_clock::now() ;

    auto elasped = end - start ;
    auto millisec = std::chrono::duration_cast<std::chrono::milliseconds>( elasped ) ;

    std::cout << millisec.count() << std::endl ;
}

問題は、C++実装は規格上、as-ifルールにしたがって、処理をリオーダーできるということだ。そのため、C++実装はdo_something関数の呼び出しをstartとendの間からどこか別の場所に移しても、規格上全く問題ない。

このas-ifルールはとても強力で、現在、C++規格には移植性のある方法であるコード辺の処理時間の計測をする方法がない。mutexやスレッドの使用はリオーダーを妨げないし、分割コンパイルですら、プログラム全体の最適化やリンク時最適化の前では通用しない。

そこで、この文書では、std::timing_fenceというコードをそのブロックを超えてリオーダーしないフェンスを追加することを提案している。

確かに入るべきだ。

[PDF] P0343R0: Meta-programming High-Order Functions

メタプログラミングのためのライブラリを追加する提案。MetaとBoost.MPLとboost.Hanaを参考に設計されている。

meta::id

引数をそのまま返すメタ関数。いわゆるidentity。

meta::eval

typename T::typeをしてくれるメタ関数

Meta-Callables型

メタ呼び出し可能型は、ネストされたテンプレートエイリアスinvokeを持つ型である。Booste.MPLならばapplyに相当する。

struct identity
 {
 template <class T>
 using invoke = T;
 };

meta::invoke

meta-callablesのinvokeを呼び出すメタ関数

その他、様々なメタプログラミングを助けるためのライブラリがある。

P0345R0: Allowing any unsigned integral type as parameter type for literal operators

ユーザー定義リテラルの仮引数の型に任意のunsigned integer型を許可する。また、縮小変換を禁止する。

std::uint8_t operator "" _foo ( unsigned long long int x ) { return x ; }

// 縮小変換がかかるが、ユーザー定義リテラルの定義を見なければ縮小変換が起こることがわからない。
auto a = 1024_foo ;
// 縮小変換がかかることが明らかなので警告できる。
std::uint8_t b = 1024 ;

[PDF] P0346R0: A <random> Nomenclature Tweak

Uniform Random Number Generatorは誤解しやすい用語なのでUniform Random Bit Generatorに改名する。

P0347R0: P0347R0 Simplifying simple uses of <random> u

乱数ライブラリを使いやすくするラッパー、random_genrator<T>の提案

現在の乱数ライブラリは初心者には使いにくい。まずUniform Random Number Generatorのオブジェクトを作り、seedを初期化して、望みのdistributionクラスのオブジェクトを作り、URNGとdistributionの2つのオブジェクトを組み合わせて乱数を作る。

提案されているライブラリは、以下のように使える。

#include <random>
#include <iostream>
int main()
{
    std::mt19937_rng rng; // nondeterministically seeded, convenience typedef for std::random_generator<std::mt19937>
    std::cout << "Greetings from Office #" << rng.uniform(1,17) // uniform integer in [1, 17]
              << " (where we think PI = "  << rng.uniform(3.1,3.2) << ")\n\n" // uniform real in [3.1, 3.2)
              << "We " << rng.pick({"welcome",
                                    "look forward to synergizing with",
                                    "will resist",
                                    "are apathetic towards"})
                       << " our management overlords\n\n";

    std::cout << "On the 'business intelligence' test, we scored "
              << rng.variate(std::normal_distribution<>(70.0, 10.0))
              << "%\n";
}

mt19937_rngは、random_generator<mt19937>へのtypedefである。random_generatorは、テンプレート実引数て指定されたURNGのオブジェクトを作り、非決定的な方法でseedする。メンバー関数のuniform(a, b)に整数か浮動小数点数を渡すと、aからbまでの範囲の乱数が生成される。pickを使うと引数に渡したコンテナーの中の要素から一つが選ばれて返される。variateは非一様分布を渡すことができる。

同等のコードをrandom_generatorを使わずに書くと、以下のようになる。

#include <random>
#include <iostream>
int main()
{
    std::random_device rd; // assume unsigned int is 32 bits
    std::seed_seq sseq {rd(), rd(), rd(), rd(), rd(), rd(), rd(), rd()};
    std::mt19937 engine(sseq); // seeded with 256 bits of entropy from random_device

    auto strings = {"welcome",
                    "look forward to synergizing with",
                    "will resist",
                    "are apathetic towards"
                   };

    std::cout << "Greetings from Office #" << std::uniform_int_distribution<>(1,17)(engine) // uniform integer in [1, 17]
              << " (where we think PI = "  << std::uniform_real_distribution<>(3.1, 3.2)(engine) << ")\n\n" // uniform real in [3.1, 3.2)
            << "We " << *(strings.begin() + std::uniform_int_distribution<>(0, std::size(strings) - 1)(engine))
                       << " our management overlords\n\n";

    std::cout << "On the 'business intelligence' test, we scored "
              << std::normal_distribution<>(70.0, 10.0)(engine)
              << "%\n";
}

ただ、このライブラリは、uniformメンバー関数を呼び出すたびに対応するdistributionクラスのオブジェクトが作られるので、効率が悪い。このライブラリは効率と引き換えに手軽さを提供するライブラリなのでこれでいいのだとしている。

P0348R0: Validity testing issues

immediate contextによるエラーはハードエラーではないというのがSFINAEの基本となっている。では、immediate contextとは何かという問題が残っている。

この文書は、現在解釈が揺れている例をいくつか上げて、委員会はこの例に対する回答を示すべきであるとしている。

deleted定義を宣言以外の方法で参照するとill-formedであると規定されているが、別の場所では、ハードエラーにはならないと規定されている。どちらが優先されるべきなのか。ハードエラーにはならないほうがSFINAEによる利用ができて好ましい。

GCCもClangも、変数テンプレートの初期化子はimmediate contextにはならないと解釈している。

関数のデフォルト実引数の中の式はimmediate contextかどうかClangとMSVCは一貫してimmediate contextではないとするが、GCCは一貫していない。

他にもまだ問題提起はある。

[PDF] P0349R0: Assumptions about the size of datapar

P0214でdataparというベクトル型の提案をしているが、size()がコンパイル時定数になっている。しかし、世の中には可変長ベクトル型をサポートしたアーキテクチャもある。ベクトル超を可変長にするのは、将来性も見込まれている。size()をコンパイル時定数にする設計は将来の足かせになると主張する文書

ドワンゴ広告

ドワンゴは本物のC++プログラマーを募集しています。

採用情報|株式会社ドワンゴ

CC BY-ND 4.0: Creative Commons — Attribution-NoDerivatives 4.0 International — CC BY-ND 4.0