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++ きびしい…。