Ruby の (1..5) と (1...5) の違い
前者は使っていたけど、後者は知らなかかったので覚書。
範囲演算子 ..
を使用した場合は終端を含み、...
を使用した場合は終端を含みません。
p (1..5).to_a # => [1, 2, 3, 4, 5] p (1...5).to_a # => [1, 2, 3, 4]
C++14(17) で zip 書いてみた
現状の C++ で zip を書いたらどうなるんだろう、と思って書いてみました。
[コード]
#include <iostream> #include <algorithm> #include <vector> #include <tuple> template<typename T, typename U> constexpr auto zip_impl(T v1, U v2){ auto vi1 = std::begin(v1); auto vi2 = std::begin(v2); using value_t = decltype(std::make_tuple(*vi1, *vi2)); std::vector<value_t> result{}; while(vi1 != std::end(v1) && vi2 != std::end(v2)){ result.emplace_back(*vi1, *vi2); vi1++; vi2++; } return result; } template<typename T, typename U> constexpr auto zip(T v1, U v2){ return zip_impl(v1, v2); } template<typename T, typename U> constexpr auto zip(std::initializer_list<T> v1, std::initializer_list<U> v2){ return zip_impl(v1, v2); } int main(){ for(auto [x, y] : zip({1, 2, 3, 4, 5}, {'a', 'b', 'c', 'd', 'e'})){ std::cout << x << ":" << y << std::endl; } return 0; }
[出力]
1:a 2:b 3:c 4:d 5:e
https://wandbox.org/permlink/qQEvmwNMjgVt3GHJ
手癖で constexpr
をつけているけど特に constexpr
ではない(std::vector
に依存してるので constexpr
には出来ない…。
使う側では C++17 の『構造化束縛』を使っているけど、実装側は C++14 でも動作すると思う。
っていうか、range based-for で構造化束縛使うのかなり強力なのではないだろうか???
実装は単純に2つのコンテナを iterator で回しながらタプルで保持するようにしてるだけですね。
特に複雑なことはしていないと思う。
最適解かどうかは別としてこういうコードが C++ でも雑に書けるようになったのはだいぶよさがあるなあ。
Ruby の () の戻り値
Ruby だと () の戻り値は nil
になるらしい。
p () # => nil p ().nil? # true
ほぼ使うことはないだろうけど知らなかった(そもそも ()
だけ評価出来ると思ってなかった。
ちなみに #call
が呼び出されるわけではない。
class X def call "call" end def func p self.() # => "call" p () # => nil end end X.new.func
C++17 のクラステンプレートのテンプレート引数推論を使ってみて
C++17 では『クラステンプレートのテンプレート引数推論』という機能が新しく追加されました。
どういう機能かというと C++17 以前では以下のようなクラステンプレートを使用する場合はテンプレート引数を明示化する必要がありました。
template<typename T> struct A{ A(T t) : t{} }; // テンプレート引数を渡して値を定義 auto a = A<int>{42};
しかし、C++17 ではこれを推論してくれるようになりました。
template<typename T> struct A{ A(T t) : t{} }; // コンストラクタの引数から T 型を推論してくれる auto a = A{42};
これによりクラステンプレートを生成する make_xxx
みたいなヘルパ関数を大幅に削減する事が出来そうですね。
上記はコンストラクタから推論していますが、他にも『推定ガイド(deduction guide)』という構文を使用しても推論する事が出来ます。
[Clang 5.0(svn306440) では一部動作しなかった]
本題というか単に Clang 5.0(svn306440) だと動作しなかった、という話ですが。
Clang 5.0(svn306440) では次のようにクラステンプレートのインナークラスだとコンパイルエラーになりました。
template<typename F> struct X{ template<class T> struct A { A(T&& t) : value(t){} T value; }; }; // error: no viable constructor or deduction guide for deduction of template arguments of 'A' auto a = X<int>::A{42};
https://wandbox.org/permlink/ylwbjUX5xs6JFbfn
X
がクラステンプレートでなかったり、GCC 8.0 では問題なかったので Clang のバグかなぁ…。
[参照]
C++ で Non-static data member initializers に auto が使えないのがつらい
C++14(17) になっても Non-static data member initializers に auto
が使えないのがつらい、という話です。
[Non-static data member initializers とは]
Non-static data member initializers とは C++11 から追加された言語機能の一つで クラスのメンバ変数を定義する時に初期値を設定できる という機能です。
C++03 では以下のようにコンストラクタでメンバ変数の初期値を設定することが出来ました。
struct X{ // コンストラクタ時に初期値を設定する X() : value(0) , value2(3.14f){} int value; float value2; };
一方、C++11 以降ではメンバ変数定義時に直截値を代入することが出来ます。
struct X{ // メンバ変数に対して直接初期値を代入する事が出来る // コンストラクタを定義する必要がない int value = 0; float value2 = 3.14f; };
これによりコンストラクタを定義する事なく、メンバ変数の初期値を設定する事が出来ます。
[Non-static data member initializers で auto
を使うことが出来ない]
さて、今の時代、auto
を使って型推論を行う時代ですね。
C++14 では戻り値型を auto
で型推論してくれたり、ラムダ式の引数に auto
を使うことで多相ラムダを実現することが出来るようになりました。
しかし、残念なことに Non-static data member initializers では auto
を使うことが出来ません。
struct X{ // 変数を定義する時に auto を使うのは至極当然…だが… // error: 'auto' not allowed in non-static struct member auto value = 0; auto value2 = 3.14f; };
なんで C++14 では戻り値型に auto
を指定したり、ラムダ式の引数にさえ auto
で型推論してくれるようになったのに!!
Non-static data member initializers では!!
auto
が!!!!
使えない!!!!!
なぜだ!!!!
なぜだ!!!!!!!!!
なぜ auto
が使えないのか理解に苦しむ
[Non-static data member initializers で auto
を使えないと何が困るのか]
さて、先ほどの例だけだと『別に型推論しなくたって普通に型を定義すればいいじゃん』と思うでしょう。 では、次の例だとどうでしょう。
struct X{ // ラムダ式をメンバ変数で保持したいよね!!! auto twice = [](int a){ return a + a; }; };
はいーラムダ式ーきたー 。
ラムダ式は定義された時に『ユニークな型』となるため、decltype()
のようなものを使っても定義時に型を指定することが出来ません。
[キャプチャしてないラムダ式を関数ポインタ型にキャストする]
と、ここで C++ に精通している方ならすでに気づいていると思いますが『キャプチャしてないラムダ式は関数ポインタ型にキャストすること』が出来ます。
ですので、上記の場合は以下のように記述することが出来ます。
struct X{ // キャプチャしてないラムダ式は関数ポインタ型にキャストされる!! std::common_type<int(*)(int)>::type twice = [](int a){ return a + a; }; };
やったね!!! と、思いますがこれもまだ不完全です。
[キャプチャしたラムダ式を std::function
で保持する]
例えば『ラムダ式内で this
を参照したい』場合には this
をキャプチャする必要があるので関数ポインタ型にキャストすることが出来ません。
struct X{ // this をキャプチャしたいんだけどなー… // error: no viable conversion from 'X::(lambda at ...)' to 'std::common_type<int (*)(int)>::type' (aka 'int (*)(int)') std::common_type<int(*)(int)>::type twice = [this](int a){ return a + a + offset; }; int offset = 42; };
なるほど???
が、これも std::function
を使うことで回避することが出来ます。
#include <functional> struct X{ // こういう時の std::function std::function<int(int)> twice = [this](int a){ return a + a + offset; }; int offset = 42; };
std::function
を使うことで多少オーバーヘッドが生じますがまあしょうがないですね。
[多相ラムダの場合]
さて、今までの回避方法はC++11 では有効な手段でした。
しかし、時代は C++14、もうすぐ C++17 もやってきます。
そう、多相ラムダです!!!
今の時代、ラムダ式の引数も型推論を行いたいですよね???
struct X{ // 関数ポインタ型にキャストされるけど、int 型で固定されてしまう… std::common_type<int(*)(int)>::type twice = [](auto a){ return a + a; }; };
本来は多相ラムダとして定義して twice(42)
や twice(3.14)
、twice(std::string("homu"))
など引数型に依存しないように使いたいですよね?
しかし、上記の場合では『関数ポインタ型を決定する時に引数型を決定する必要がある』ので型推論を行うことが出来ません。
この問題は std::function
を使っても同様です。
[キャプチャを行わない場合の回避方法]
回避方法としては一度、メンバ変数以外で定義してからメンバ変数に代入するということは出来ます。
struct X{ // 一度、メンバ変数以外で定義して static constexpr auto twice_ = [](auto self, auto it) constexpr{ return it + it; }; // その変数をメンバ変数として代入する decltype(X::twice_) twice = X::twice_; };
しかし、この場合は this
をキャプチャすることが出来ないのであまりメンバ変数として保持する意味がありません…。
[メンバ関数テンプレートを使う]
そもそも『メンバ変数ではなくてメンバ関数テンプレートとして定義すればいいのでは?』と思う人が多いと思います。
全く持ってそのとおりで通常は素直にメンバ関数テンプレートを定義すれば解決します。
struct X{ // 余計なメンバ変数も定義されないし、this も参照できるし完璧 template<typename T> constexpr auto twice(T t) const{ return t + t + offset; } int offset = 42; };
[メンバ関数テンプレートをオブジェクトとして扱いたかった]
さて、本題というかやりたかったことですが、単純に『メンバ関数テンプレートをオブジェクトとして扱いたかった』からです。
例えば、次のようなことは本来 C++ では行うことは出来ません。
struct X{ template<typename T> constexpr auto twice(T t) const{ return t + t + offset; } int offset = 42; }; X x{}; // x.twice をオブジェクトとして扱いたかったが… // error: reference to non-static member function must be called auto x_twice = x.twice; // x.twice を呼び出す x_twice(42);
上記のように『メンバ関数をオブジェクトとして扱う』場合には関数を『メンバ関数として定義する』のは不向きです。
そしてこれは『メンバ関数をラムダ式として定義する』ことで実現する事が出来ます。
struct X{ // 本来は auto で定義したい… // auto twice = [this](auto it){ std::function<int(int)> twice = [this](auto it){ return it + it + this->offset; }; int offset = 42; }; X x{}; std::cout << x.twice(42) << std::endl; // x.twice をオブジェクトとして扱える auto x_twice = x.twice; // x.twice を呼び出す std::cout << x_twice(12) << std::endl;
このようなことを実現したい場合は Non-static data member initializers で auto
を使うしかないのです…。
[まとめ]
C++14 や C++17 ではあちこちで型推論ができようになったのに Non-static data member initializers で auto
が使えない仕様は控えめにいってクソだと思いました。
[参照]
C++ で関数引数の評価順序を保証したかった
[関数引数の評価順序]
C++では関数を呼び出す際に渡される引数の評価順序は未規定になっています。
どういうことかというと次のようなコードの場合、処理系によって出力される値が異なる可能性があります。
template<typename T> auto print(T value){ std::cout << value; return value; } template<typename... Args> void func(Args&&...){} // 123 と出力されるかもしれないし 321 と出力されるかもしれない func(print(1), print(2), print(3));
上記のようなコードの場合、 clang では 123
と出力されますが、gcc では 321
と出力されます。
ただし、これは関数呼び出しの場合のみで、コンストラクタの {}
呼び出しや、初期化リストの {}
では順序が『左から右の順で評価される』ことが保証されています。
ですので次のような初期化リストを呼び出した場合は期待通りの動作を行います。
template<typename T> auto print(T value){ std::cout << value; return value; } // 123 と出力されることが保証されている auto dummy = { print(1), print(2), print(3) };
[可変長テンプレート引数で引数分関数を呼び出したい]
上記の仕様を踏まえた上で、次のような処理を考えてみます。
template<typename F, typename... Args> auto call(F f, Args... args){ // 可変長引数の数だけ f を呼び出してその結果を tuple で返す return std::make_tuple(f(args)...); } using namespace std::literals::string_literals; auto twice = [](auto a){ return a + a; }; auto result = call(twice, 1, 3.5, "homu"s); assert(result == std::make_tuple(2, 7.0, "homuhomu"));
『可変長引数の数だけ f
を呼び出してその結果を tuple
として返す』という簡単な関数です。
しかし、残念なことに std::make_tuple(f(args)...)
という関数呼び出しでは、評価順序が処理系によって異なってしまいます。
auto print = [](auto a){ std::cout << a << std::endl; return a; }; // print の呼び出しが不規定なのでどういう出力になるのかが保証されてない call(print, 1, 3.5, "homu"s);
[std::make_tuple
ではなくて std::tuple
コンストラクタを使う]
上記の問題は std::make_tuple
ではなくて std::tuple
のコンストラクタを呼び出すことで回避できます。
template<typename F, typename... Args> auto call(F f, Args... args){ // std::tuple のコンストラクタを呼び出すことで評価順序を保証する return std::tuple<decltype(f(args))...>{f(args)...}; } auto print = [](auto a){ std::cout << a << std::endl; return a; }; // 出力結果が保証される call(print, 1, 3.5, "homu"s);
これで意図した順番で評価される事が保証されます。
[参照]
C++ の可変引数リストに non trivial な値を渡すとコンパイルエラーになる
以下のようなコードを Clang でコンパイルしたところエラーになりました。
[](...){}(std::string("homu")); // error: cannot pass object of non-trivial type 'std::string' (aka 'basic_string<char, char_traits<char>, allocator<char> >') through variadic method; call will abort at runtime [-Wnon-pod-varargs]
gcc だと問題なかったのでなんでだろーと思って調べていたらどうやら未定義の動作みたい。
参照:可変引数リストと非PODクラス型の関係 - yohhoyの日記
件のコードだとラムダ式ですが、通常の関数定義も同様です。
ちなみに variadic template として受け取れば回避出来ました。
[](auto...){}(std::string("homu")); // ok
C++ きびしい…。