Effective Modern C++ #7Effective Modern C++ #7
2015/07/15
清⽔水超貴@simizut22
ちょっと⾃自⼰己紹介ちょっと⾃自⼰己紹介
清⽔水超貴(@simizut22)
数理計画がお仕事
基本荷物を積む
たまにグラフの対称性⾒見つける
やっぱり荷物を積む
closureclosureに変数をに変数をmovemoveするならするなら
初期化キャプチャ初期化キャプチャ――を使おうを使おう
変数を変数をmove capturemove captureしたいしたい
class Widget {
/* ... */
};
std::unique_ptr< Widget > pw;
auto fun = [pw]{ /* do something */ };
上記コード(compile 不可)
container などをcopyしたくない
などのとき
初期化キャプチャの構⽂文初期化キャプチャの構⽂文
value capture
reference capture
[ data = (expression) ]
[&data = (expression)]
auto cmp =
[eval = [](int i){ return -i;}] (int lhs, int rhs)
{ return eval(lhs) < eval(rhs);}
expression なので lambda を⼊入れるのももちろん可能
ちなみに,最新の gcc/msvc は次もok
http://melpon.org/wandbox/permlink/Gmqycg5tbA2iE51S
using iVec = std::vector< int >;
iVec data = iVec{1, 2, 3, 4, 5};
auto fun = [data{std::move(data)}]
{ std::copy(std::begin(data),
std::end(data),
[] () mutable
{ data.clear(): }};
prog.cc:9:19: warning:
direct list initialization of a lambda init-capture will change meaning in a future version of Cla
insert an '=' to avoid a change in behavior [-Wfuture-compat]
auto f = [data{ std::move(data) } ] {
^
clang だと次の messageが出る
ref:
N3681 Auto and Braced-Init-Lists
N3912 Atuto and Braced-Init-Lists, continued
N3922 New Rules for auto deduction from braced-init-list
template< class T >
struct wrap {
mutable T value_;
wrap(T&& v) : value_(std::forward< T >(v)) {}
wrap(wrap const& other) : value_(std::move(other.value_)) {}
};
struct Widget {};
auto pw = wrap< std::unique_ptr< Widget > >{std::make_unique< Widget >()};
auto fun = [pw]{ /* do something */ }
こんな wrapper を書くとか...
ref:N3610: Generic lambda-capture initializers
C++14 なら,初期化キャプチャーでできる
class Widget {
public:
/* ... */
bool isValidated() const;
bool isProessed() const;
bool isAchieved() const;
private:
/* ... */
};
auto pw = std::make_unique< Widget >();
/**
* configure *pw
**/
auto func = [pw = std::move(pw)]
{ return pw->isValidated()
&& pw->isAchived();};
/**
* pw is moved to data member pw of closure
**/
こうなる
上記のコードでは
1. closureにデータメンバー pw を作成して
2. local 変数 pw を move して
3. メンバーpw を初期化
をやっている
[pw = std::move(pw)] { /* do something */};
もし configure pw の操作が不要なら,
初期化を capture と同時にやればよい
[pw = std::make_unique< Widget >()] () { /* do something */ };
lambdalambda 使わなくていいならクラス書けば解決で使わなくていいならクラス書けば解決で
きるきるclass Widget {
/* same as before */
};
class IsValAndArch {
public:
using DataType = std::unique_ptr< Widget >;
explicit IsValAndArch(DataType&& dt)
: pw(std::move(dt)) {}
bool operator()() const {
return pw->isValid()
&& pw->isArchived();
}
private:
DataType pw;
};
auto func = IsValAndArch{ std::unique_ptr< Widget >{new Widget()}};
// auto func = IsValAndArch{ std::make_unique< Widget >() };
疲れる.やっぱり lambda 使いたい...
それそれ bindbind でできるよでできるよ!!!!
1. (move)キャプチャーしたい変数を bind object 内にmoveする
2. "キャプチャー"した変数の参照をラムダの引数にする
をすればよい
/** c++ 14 init capture **/
using dVec =
std::vector< double >;
dVec data;
/* configure data */
auto func =
[data = std::move(data)]
{ /* do somthing */ };
/** use std::bind for c++11 **/
using dVec =
std::vector< double >;
dVec data;
/* configure data */
auto func =
std::bind([](dVec const&)
// (2) ^^^^^^^^^^^^
{/* do somthing */}
, std::move(data));
// (1) ^^^^^^^^^^^^^^^
具体的なコードは以下
bindbind とと closureclosure の違いの違い
/** c++ 14 init capture **/
std::vector< double > data;
/* cofigure data */
auto func =
[data = std::move(data)]
{ data.clear() };
/** bind version **/
std::vector< double > data;
/** config data **/
auto func =
std::bind([](std::vector< double > v&)
{ v.clear(); }
, std::move(data));
これを愚直に書き換えると
http://melpon.org/wandbox/permlink/3bIhkKfDDlQhXREr
Start
prog.cc:7:19: error: member function 'clear' not viable: 'this' argument has type 'const std::__1:
{ data.clear(); };
^~~~
/usr/local/libcxx-head/include/c++/v1/vector:735:10: note: 'clear' declared here
void clear() _NOEXCEPT
^
1 error generated.
clang3.7.0
build してみた
ClosureType::operator() は
デフォルトではconst method
mutable specifier を付けると non-const method
が呼ばれる(上は const method を呼んでる)
auto fun = [data = std::move(data)] () mutable
{ data.clear(); }; // ^^^^^^^
これで⼤大丈夫!(^^)!
** mutable を付けるときには argument parameter list が必要
http://melpon.org/wandbox/permlink/zzoa3WK54NxBz7Ac
bind object の⽣生存期間
auto makeFun() // sorry this is c++14
{
std::vector< int > data{1,2,3,4,5};
auto fun = [](const std::vector< int >& v)
{ std::copy(v.begin(), v.end(),
std::ostream_iterator< int >(std::cout, " "));};
return std::bind(fun, std::move(data)); // data is copied in bind obj
}
auto&& fun = makeFun();
fun(); // ok
先に述べた bind + lambda を⽤用いた⽅方法を使った時
bind object の⽣生存期間
= closure object(first argument )の⽣生存期間
= move "capture" された data の⽣生存期間
がなりたつ
従って,capture された data を closure から呼ぶことに問題はない
1. c++11 lambda では closure に move-construct はできないけど,
bind object にはできる
2. bind-object 内に move-construct してから,lambda に参照渡し
することで move キャプチャを模倣できる
3. 上記⽅方法では bind-object の寿命と closure の寿命が同じなの
で, move されたデータはキャプチャされたように扱える
bindbind によるによる init captureinit capture の模倣の要の模倣の要
点点
* ここまでやったけど Item34 では bind より lambda の⽅方がいいという話をする
Things to RememberThings to Remember
closure 内に objects を move するなら c++14 init
capture を使おう
直にクラスを書くか bind を使えば init-capture は
emulate できる

emc++ chapter32

  • 1.
    Effective Modern C++#7Effective Modern C++ #7 2015/07/15 清⽔水超貴@simizut22
  • 2.
  • 3.
  • 4.
    変数を変数をmove capturemove captureしたいしたい classWidget { /* ... */ }; std::unique_ptr< Widget > pw; auto fun = [pw]{ /* do something */ }; 上記コード(compile 不可) container などをcopyしたくない などのとき
  • 5.
    初期化キャプチャの構⽂文初期化キャプチャの構⽂文 value capture reference capture [data = (expression) ] [&data = (expression)] auto cmp = [eval = [](int i){ return -i;}] (int lhs, int rhs) { return eval(lhs) < eval(rhs);} expression なので lambda を⼊入れるのももちろん可能
  • 6.
    ちなみに,最新の gcc/msvc は次もok http://melpon.org/wandbox/permlink/Gmqycg5tbA2iE51S usingiVec = std::vector< int >; iVec data = iVec{1, 2, 3, 4, 5}; auto fun = [data{std::move(data)}] { std::copy(std::begin(data), std::end(data), [] () mutable { data.clear(): }}; prog.cc:9:19: warning: direct list initialization of a lambda init-capture will change meaning in a future version of Cla insert an '=' to avoid a change in behavior [-Wfuture-compat] auto f = [data{ std::move(data) } ] { ^ clang だと次の messageが出る ref: N3681 Auto and Braced-Init-Lists N3912 Atuto and Braced-Init-Lists, continued N3922 New Rules for auto deduction from braced-init-list
  • 7.
    template< class T> struct wrap { mutable T value_; wrap(T&& v) : value_(std::forward< T >(v)) {} wrap(wrap const& other) : value_(std::move(other.value_)) {} }; struct Widget {}; auto pw = wrap< std::unique_ptr< Widget > >{std::make_unique< Widget >()}; auto fun = [pw]{ /* do something */ } こんな wrapper を書くとか... ref:N3610: Generic lambda-capture initializers C++14 なら,初期化キャプチャーでできる
  • 8.
    class Widget { public: /*... */ bool isValidated() const; bool isProessed() const; bool isAchieved() const; private: /* ... */ }; auto pw = std::make_unique< Widget >(); /** * configure *pw **/ auto func = [pw = std::move(pw)] { return pw->isValidated() && pw->isAchived();}; /** * pw is moved to data member pw of closure **/ こうなる
  • 9.
    上記のコードでは 1. closureにデータメンバー pwを作成して 2. local 変数 pw を move して 3. メンバーpw を初期化 をやっている [pw = std::move(pw)] { /* do something */}; もし configure pw の操作が不要なら, 初期化を capture と同時にやればよい [pw = std::make_unique< Widget >()] () { /* do something */ };
  • 10.
    lambdalambda 使わなくていいならクラス書けば解決で使わなくていいならクラス書けば解決で きるきるclass Widget{ /* same as before */ }; class IsValAndArch { public: using DataType = std::unique_ptr< Widget >; explicit IsValAndArch(DataType&& dt) : pw(std::move(dt)) {} bool operator()() const { return pw->isValid() && pw->isArchived(); } private: DataType pw; }; auto func = IsValAndArch{ std::unique_ptr< Widget >{new Widget()}}; // auto func = IsValAndArch{ std::make_unique< Widget >() }; 疲れる.やっぱり lambda 使いたい...
  • 11.
    それそれ bindbind でできるよでできるよ!!!! 1.(move)キャプチャーしたい変数を bind object 内にmoveする 2. "キャプチャー"した変数の参照をラムダの引数にする をすればよい /** c++ 14 init capture **/ using dVec = std::vector< double >; dVec data; /* configure data */ auto func = [data = std::move(data)] { /* do somthing */ }; /** use std::bind for c++11 **/ using dVec = std::vector< double >; dVec data; /* configure data */ auto func = std::bind([](dVec const&) // (2) ^^^^^^^^^^^^ {/* do somthing */} , std::move(data)); // (1) ^^^^^^^^^^^^^^^ 具体的なコードは以下
  • 12.
    bindbind とと closureclosureの違いの違い /** c++ 14 init capture **/ std::vector< double > data; /* cofigure data */ auto func = [data = std::move(data)] { data.clear() }; /** bind version **/ std::vector< double > data; /** config data **/ auto func = std::bind([](std::vector< double > v&) { v.clear(); } , std::move(data)); これを愚直に書き換えると
  • 13.
    http://melpon.org/wandbox/permlink/3bIhkKfDDlQhXREr Start prog.cc:7:19: error: memberfunction 'clear' not viable: 'this' argument has type 'const std::__1: { data.clear(); }; ^~~~ /usr/local/libcxx-head/include/c++/v1/vector:735:10: note: 'clear' declared here void clear() _NOEXCEPT ^ 1 error generated. clang3.7.0 build してみた ClosureType::operator() は デフォルトではconst method mutable specifier を付けると non-const method が呼ばれる(上は const method を呼んでる) auto fun = [data = std::move(data)] () mutable { data.clear(); }; // ^^^^^^^ これで⼤大丈夫!(^^)! ** mutable を付けるときには argument parameter list が必要 http://melpon.org/wandbox/permlink/zzoa3WK54NxBz7Ac
  • 14.
    bind object の⽣生存期間 automakeFun() // sorry this is c++14 { std::vector< int > data{1,2,3,4,5}; auto fun = [](const std::vector< int >& v) { std::copy(v.begin(), v.end(), std::ostream_iterator< int >(std::cout, " "));}; return std::bind(fun, std::move(data)); // data is copied in bind obj } auto&& fun = makeFun(); fun(); // ok 先に述べた bind + lambda を⽤用いた⽅方法を使った時 bind object の⽣生存期間 = closure object(first argument )の⽣生存期間 = move "capture" された data の⽣生存期間 がなりたつ 従って,capture された data を closure から呼ぶことに問題はない
  • 15.
    1. c++11 lambdaでは closure に move-construct はできないけど, bind object にはできる 2. bind-object 内に move-construct してから,lambda に参照渡し することで move キャプチャを模倣できる 3. 上記⽅方法では bind-object の寿命と closure の寿命が同じなの で, move されたデータはキャプチャされたように扱える bindbind によるによる init captureinit capture の模倣の要の模倣の要 点点 * ここまでやったけど Item34 では bind より lambda の⽅方がいいという話をする
  • 16.
    Things to RememberThingsto Remember closure 内に objects を move するなら c++14 init capture を使おう 直にクラスを書くか bind を使えば init-capture は emulate できる