2016-05-21 10 views
4

は、次のクラスを考えてみましょう:もちろんこの場合、最も適切なコンストラクタが呼び出されないのはなぜですか?

class foo { 
    int data; 
public: 
    template <typename T, typename = enable_if_t<is_constructible<int, T>::value>> 
    foo(const T& i) : data{ i } { cout << "Value copy ctor" << endl; } 

    template <typename T, typename = enable_if_t<is_constructible<int, T>::value>> 
    foo(T&& i) : data{ i } { cout << "Value move ctor" << endl; } 

    foo(const foo& other) : data{ other.data } { cout << "Copy ctor" << endl; } 

    foo(foo&& other) : data{ other.data } { cout << "Move ctor" << endl; } 

    operator int() { cout << "Operator int()" << endl; return data; } 
}; 

それは参照の任意の種類によって単一intを取るためにあまり意味がありませんが、これはあくまでも一例です。 dataメンバは、コピーするのに非常にコストがかかる可能性があります。したがって、すべての移動セマンティクス。

このファンシーテンプレートは、基本的にdataを構築できるあらゆるタイプを可能にします。したがって、fooオブジェクトは、この基準を満たす任意の型の値をコピーまたは移動するか、単にタイプfooの別のオブジェクトをコピーまたは移動することによって構築することができます。今のところかなりまっすぐです。あなたはこのような何かをしようとすると、

問題が発生します。

foo obj1(42); 
foo obj2(obj1); 

この(私の意見ではLESTで)やるべきこと(これに値42を移動することで最初のオブジェクトを構築することですそれはrvalueなので)、最初のオブジェクトをコピーして2番目のオブジェクトを構築します。だから、これはプリントアウトしなければならないものです。

Value move ctor 
Copy ctor 

をしかし、それは実際にプリントアウトすることです:

Value move ctor 
Operator int 
Value move ctor 

第一の目的はそこに、何の問題をうまく構築されていないされます。しかし、コピーコンストラクタを呼び出して第2のオブジェクトを構築する代わりに、プログラムは最初のオブジェクトを別の型に変換してから、その型から移動できるfooという別のコンストラクタを呼び出します(その時点のrvalueです)。 )。

これは非常に奇妙なことですが、このコードからはっきりとした動作ではありません。コピーコンストラクタを2番目のオブジェクトの作成時に呼び出す方が理にかなっていると思います。

ここで何が起こるか説明できますか?もちろん、私はintへのユーザ定義の変換があるので、これは完全に有効なパスですが、私はそれを理解できません。なぜコンパイラは、指定された値とまったく同じ引数型を持つコンストラクタを単に呼び出すことを拒否しますか?それが最も簡単なことではないでしょうか?したがって、デフォルトの動作ですか?変換演算子を呼び出すとコピーも実行されるので、単にコピーコンストラクターを呼び出すよりも速く、最適ではないと思います。

+1

現在のようにis_constructibleではなく、代わりにではありませんか? – Alejandro

+0

@Alejandro Correct、それも[問題を解決する](https://ideone.com/6SRrGr)です。 – nwp

+0

@nwp私は混乱しています。 [this](http://en.cppreference.com/w/cpp/types/is_constructible)によると、そのことは、**拳のこと**が第2のものから構築できるかどうかを返します**。そして 'T'引数から' data'メンバ( 'int')を構築しているので、これは正しい順序でなければなりません。私は 'int'を構築できる型しか受け入れません。 – adam10603

答えて

5

あなたのテンプレート「移動」コンストラクタは、それが唯一のconst foo &がかかるので、あなたのコピーコンストラクタよりも一致であるタイプfoo &のパラメータを持っています。テンプレートコンストラクタでdataが入力され、iintに変換され、operator int()が呼び出されます。 、Tは左辺値参照型と推定される場合には(あまりにも左辺値参照に崩壊してしまうあなたT&& iを意味する)代替の失敗を強制:

最も簡単即時修正は、操作を移動するためにあなたの移動のコンストラクタを制限するenable_ifを使用することができます。

注:パラメータには名前があり、他のすべての名前付きオブジェクトと同様に、コンストラクタ内には名前があります。これは左辺値です。そこから移動したいと考えると、std::moveを使うことができます。

より一般的に、あなたは(左辺値と右辺値の両方を受け入れる)完璧な転送を使用することができ、かつ唯一の特別な例外としてfoo自体を削除します。

template <typename T, typename = enable_if_t<is_constructible<int, T>::value> 
        , typename = enable_if_t<!is_same<decay_t<T>, foo>::value> 
foo(T&& i) : data(std::forward<T>(i)) { cout << "Forwarding ctor" << endl; } 

これはあなたの価値コピー値ムーブコンストラクタを置き換えます。

別のノート:is_constructible<int, T>::valuedata(std::forward<T>(i))が整形式であるかどうかがわかりますテストです。 data{std::forward<T>(i)}がうまく形成されるかどうかは、ではなく、のテストになります。 longintの変換は狭変換であるため、結果が異なるは{}では許されません。

+0

これは可能な解決策のようですが、私はあなたが何を意味するのか正確には分かりません。あなたはそれを少し良く説明できますか? – adam10603

+0

これは機能します!私は、左辺値参照が左辺値参照に何らかの形で崩壊する可能性があることは知らなかった。ありがとう! – adam10603

+0

あなたが追加した他のものも非常に便利です。私はそれらの事のいくつかを考えなかった。ありがとう – adam10603

0

operator int()のために発生します。

operator int()のため、obj1operator int()を呼び出し、intとしてキャストします。


考えられる解決策:

  1. 使用static_cast<foo>。通常はキャストする変数をintとしてfooにキャストします。

  2. 誰でもこの質問を編集して記入してください。もうアイデアはありません。 (T = foo &付き)

+0

@Alejandroが言ったこともまた真実です。 –

+0

私はそれが可能な理由を知っていますが、本当になぜこれが問題なのでしょうか。引数( 'foo')の正確な型を取るコンストラクタがあるので、変換は行われません。クラスが 'foo'オブジェクトを受け入れたコンストラクタを持っていなかった場合、これは完全に正常ですが、そうするため、変換をトリガするよりも些細なルートを取るべきです。また、 'static_cast'は問題を解決しません。私が渡した値は既に 'foo'型です。それをキャストする必要はありません。 – adam10603

+0

多分あなたは 'operator foo()'を作ることができるかもしれません –

関連する問題