2015-09-30 5 views
14

関数が参照を返しても返される型が明示的に参照として呼び出されていない場合、lambdaから作成されたstd :: functionsに問題があります。 std :: functionは警告なしでうまく作成されているようですが、それを呼び出すと、参照が予想されるときに値が返され、物事が爆発します。ここでは非常に不自然な例だ:std :: functionを使った奇妙な返り動作(lambda(C++)から作成)

#include <iostream> 
#include <vector> 
#include <functional> 

int main(){ 
    std::vector<int> v; 
    v.push_back(123); 
    std::function<const std::vector<int>&(const std::vector<int>&)> callback = 
     [](const std::vector<int> &in){return in;}; 
    std::cout << callback(v).at(0) << std::endl; 
    return 0; 
} 

これは、ラムダが明示的にそれが正常に動作しますconst参照を返すように変更された場合しかし、ゴミを出力します。私はコンパイラがラムダがヒント無しに戻り値であると考えていることを理解することができます(私はこの問題に当初直面していましたが、ラムダはconst参照を返す関数から結果を直接返していました。 const参照リターンのラムダは推論可能ですが、明らかにそうではありません。)私が驚いているのは、コンパイラはstd ::関数をラムダから返される型が一致しないように構築できるということです。この動作は期待されていますか?このミスマッチを起こすことができる標準で何かが見当たりませんか?私はg ++(GCC)4.8.2でこれを見ていますが、他の何ものでも試していません。

ありがとうございます!

+2

@Nawazなぜあなたはあなたの答えを削除しましたか? – Barry

+1

Clang ++ 3.7.0でもガベージが表示されます(g ++はセグメンテーション違反につながります)。 – jhnnslschnr

+0

@jhnnslschnrチェックしていただきありがとうございます - なぜ投稿された回答が削除されたのかわかりませんが(正しいと思われますが)、本質的には値を返すラムダが参照戻り値の型を持つstd ::関数に幸せに結びつくことでした。ラムダはコピーを返し、関数は一時コピーへの参照を返します。これは、参照の戻り値の型を持つ関数からローカル変数を返すことと同じだと思います。 – Kevin

答えて

12

なぜ壊れていますか?

ラムダの戻り値の型が推定されると、参照とcvの資格が削除されます。だから、

[](const std::vector<int> &in){return in;}; 

の戻り値の型は、ちょうどstd::vector<int>、ないstd::vector<int> const&です。私たちは、ラムダとあなたのコードのstd::function一部を取り除く場合、結果として、我々は効果的に持っている:

std::vector<int> lambda(std::vector<int> const& in) 
{ 
    return in; 
} 

std::vector<int> const& callback(std::vector<int> const& in) 
{ 
    return lambda(in); 
} 

lambdaは一時的に返します。それは効果的にその入力を効果的にコピーします。この一時的なものは、callbackの参照リターンにバインドされています。しかし、returnステートメントの参照にバインドされた一時変数の存続期間は延長されないため、一時的なものはreturnステートメントの最後に破棄されます。したがって、この時点で:

callback(v).at(0) 
-----------^ 

我々は破壊されコピーvにダングリング参照を持っています。

ソリューションは、明示的に参照するラムダの戻り値の型を指定することです:

[](const std::vector<int> &in)-> const std::vector<int>& {return in;} 
[](const std::vector<int> &in)-> decltype(auto) {return in;} // C++14 

は今何のコピー、ノー一時、無ダングリング参照、および無未定義の動作はありません。

誰が間違っていますか?

これが期待される動作であるかどうかについては、実際は「はい」です。 std::functionのconstructibility条件は[func.wrap.func.con]である:

fは、引数の型ArgTypes...と戻り型R用(20.9.12.2)呼び出し可能です。

ここで、[func.wrap。FUNC]:式 INVOKE (f, declval<ArgTypes>()..., R)は、未評価のオペランド(条項5)と考え、ウェル形成 (20.9あれば

タイプFの呼び出し可能オブジェクトfは、引数の型ArgTypesため呼び出し可能とタイプRを返します2)。 [func.require]、強調鉱山

RCVvoidある場合

static_cast<void>(INVOKE (f, t1, t2, ..., tN))としてINVOKE(f, t1, t2, ..., tN, R)の定義は、別段INVOKE(f, t1, t2, ..., tN)暗黙Rに変換します。私たちが持っていた場合

ので、:INVOKE(func)が整形式であり、それはTを返しながら、TT const&に暗黙的に変換です:

T func(); 
std::function<T const&()> wrapped(func); 

実際にすべての標準的な要件を満たしていること。これはgccやclangのバグではありません。このような構造を許可したいと思う理由がわからないので、これはおそらく標準的な欠陥です。 は決してとなりますので、Rが参照型の場合は、Fも参照型を返す必要があります。

+1

C++ 14では、 ' - > decltype(auto)'にすることができます。 – 0x499602D2

+0

@ 0x499602D2よろしくお願いいたします。ありがとうございます。 – Barry

+0

タイプ控除については真ですが、質問の残りの半分には答えませんでした。簡体字: 'function 'が 'T 'を値で返すラムダにバインドすることを許可されているのはなぜですか?これは、常に*ぶら下がっている参照を作成します。私はこれがライブラリのバグか標準で見落とされていると言うでしょう。このケースはコンパイル時に 'decltype'を使ってチェックできるからです。 –

2

私はstd::functionコンストラクタに関する自分自身の検索を少し行いました。この部分は、std::functionと標準のCallable conceptの相互作用における見落としであるようです。 std::function<R(Args...)>::function<F>(F)は、FCallableとなるようにR(Args...)とする必要があります。 R(Args...)ためCallableも、それ自体に合理的なようである、種類Args...の与えられた引数がRに暗黙的に変換するとき(Fの戻り値の型が必要です。Rconst R_ &ときさて、これはconst R_ &R_の暗黙的な変換が可能になりますconstの参照があるため、右辺値に結合させる。これは、Egはintを返す関数f()を考える。必ずしも安全ではないではありませんが、const int &()として呼び出し可能と考えられている。

const int &result = f(); 
if (f == 5) 
{ 
    // ... 
} 

が原因で拡張するためのC++のルールをここに何の問題がありません一時的な生涯。 ただし、以下の動作は未定義た:

std::function<const int &()> fWrapped{f}; 
if (fWrapped() == 5) 
{ 
    // ... 
} 

寿命の延長が、ここでは適用されないためです。テンポラリはstd::functionoperator()の内部に作成され、比較の前に破棄されます。

したがって、std::functionのコンストラクタは、おそらく一人でCallableに依存しているが、参照にバインドするためにconst左辺値と右辺値の暗黙的な変換が禁止されている追加の制限を強制してはなりません。代わりに、Callableは、この変換を許可しないように変更することができます。ただし、安全な使用を拒否することを犠牲にしています(寿命延長のためだけです)。

さらに複雑にするには、上記の例のfWrapped()は、ダングリングリファレンスのターゲットにアクセスしない限り、完全に安全です。

関連する問題