web-dev-qa-db-ja.com

callableの完全な転送

R()- likeをvoid()- like呼び出し可能オブジェクトに変換する次のコードを思いつきました。

#include <utility>

template<class Callable>
auto discardable(Callable&& callable)
{ return [&]() { (void) std::forward<Callable>(callable)(); }; }
//        ^-- is it ok?

int main()
{
    auto f = discardable([n=42]() mutable { return n--; });
    f();
}

参考までに攻略について心配です。

  1. 明確に定義されていますか?
  2. callableが決してコピーされず、存続期間が終了した後に使用されないことを保証しますか?

これはC++ 14とタグ付けされていますが、以下のすべての標準に適用されます。

16
YSC

ラムダはoperator()を持つ匿名の構造体です。キャプチャリストはそのメンバーのタイプを指定するための特別な方法です。参照によるキャプチャーは、実際にはそれがどのように聞こえるかということです。参照メンバーがいます。参照先をぶら下げるのは難しくありません。

これは、具体的にしないでください完全に転送したい場合です。引数が左辺値か右辺値の参照かによって、セマンティクスは異なります。

template<class Callable>
auto discardable(Callable& callable)
{
    return [&]() mutable { (void) callable(); };
}

template<class Callable>
auto discardable(Callable&& callable)
{
    return [callable = std::forward<Callable>(callable)]() mutable {  // move, don't copy
        (void) std::move(callable)();  // If you want rvalue semantics
    };
}
13
Passer By

callableはxvalueである可能性があるため、ラムダキャプチャの前に破壊される可能性があり、キャプチャ内にぶら下がっている参照が残ります。これを防ぐには、引数がr値の場合、コピーする必要があります。

実際の例:

_template<class Callable>
auto discardable(Callable&& callable) { // This one makes a copy of the temporary.
    return [callable = std::move(callable)]() mutable {
        static_cast<void>(static_cast<Callable&&>(callable)());
    };
}

template<class Callable>
auto discardable(Callable& callable) {
    return [&callable]() mutable {
        static_cast<void>(callable());
    };
}
_

callableがl値参照の場合でも、存続期間の問題に直面する可能性がありますが、その存続期間のスコープは、discardableによって返されるラムダキャプチャのそれよりも小さくなります。したがって、callableを常に移動またはコピーするのが最も安全で簡単です。

補足として、- _std::apply_標準ライブラリアルゴリズム のように、関数オブジェクトの値カテゴリを完全に転送する新しい特殊なユーティリティが常にあります関数オブジェクトを値で受け入れる。つまり、operator()()&operator()()&&の両方をオーバーロードした場合、標準ライブラリは常にoperator()()&を使用します。

7

キャプチャされたラムダのぶら下がり参照を使用するため、プログラムはUBです。

したがって、 ラムダでの完全なフォワードキャプチャ を使用するには、

template<class Callable>
auto discardable(Callable&& callable)
{
    return [f = std::conditional_t<
             std::is_lvalue_reference<Callable>::value,
             std::reference_wrapper<std::remove_reference_t<Callable>>,
             Callable>{std::forward<Callable>(callable)}]
    { 
        std::forward<Callable>(f)(); 
    };
}

一時的なラムダを移動構築します。

5
Jarod42