2015-09-24 8 views
8

私が削除したコピー&移動ctorの持つクラスを持っています。のstd ::コピー不能のためのタプルと非可動オブジェクト

struct A 
{ 
    A(int a):data(a){} 
    ~A(){ std::cout << "~A()" << this << " : " << data << std::endl; } 

    A(A const &obj) = delete; 
    A(A &&obj) = delete; 

    friend std::ostream & operator << (std::ostream & out , A const & obj); 

    int data; 
}; 

そして、このクラスのオブジェクトでタプルを作成したいと思います。しかし、次のようにコンパイルされません。一方

auto p = std::tuple<A,A>(A{10},A{20}); 

を、以下コンパイルを行いますが、意外な出力を提供します。

int main() { 
    auto q = std::tuple<A&&,A&&>(A{100},A{200}); 
    std::cout << "q created\n"; 
} 

出力

~A()0x22fe10 : 100 
~A()0x22fe30 : 200 
q created 

これは、オブジェクトに対するデストラクタは、すぐタプル作図線が終了すると呼ばれていることを意味しています。だから、破壊されたオブジェクトのタプルの意味は何ですか?

+3

'のstd ::タプル(A {100}、{200} A);'ありますダングリングリファレンスのタプル。私は 'std :: tuple p(100,200);が代わりに動作するはずです –

+1

@PiotrSkotnicki clangまたはg ++でコンパイルされません:http://coliru.stacked-crooked.com/a/1fe2146b7881e241 – NathanOliver

+0

@NathanOliver it 'libC++'またはそれより新しい 'libstdC++' –

答えて

10

これは悪いです:

auto q = std::tuple<A&&,A&&>(A{100},A{200}); 

はあなたが宙ぶらりんの参照が残っているので、式の終わりに破壊されます一時に右辺値参照のtupleを構築しています。

正しい文は次のようになります。

std::tuple<A, A> q(100, 200); 

しかし、ごく最近まで、上記は標準でサポートされていませんでした。 N4296では、tupleに関連するコンストラクタ周りの文言は、[tuple.cnstr]です:sizeof...(Types) == sizeof...(UTypes)

が必要です。 is_constructible<Ti, Ui&&>::valueはすべて (i)です。
影響std::forward<UTypes>(u)内の対応する値との組の要素を初期化します。
備考UTypesの各タイプが Types内の対応するタイプに暗黙的に変換されないかぎり、このコンストラクタはオーバーロードの解決に参加してはなりません。

intAに暗黙的に変換ではないのでので、このコンストラクタはオーバーロードの解決に参加していませんでした。

struct D { D(int); D(const D&) = delete; };  
std::tuple<D> td(12); // Error 

このコンストラクタの新しい文言はN4527から、次のとおりです:これは正確にあなたのユースケースを取り上げImproving pair and tupleの採用によって解決されている

備考:このコンストラクターてはなりませんすべてiの場合にsizeof...(Types) >= 1is_constructible<Ti, Ui&&>::valueが真でない限り、過負荷解決に参加してください。コンストラクタは、少なくとも1つの場合 is_convertible<Ui&&, Ti>::valuefalseの場合は明示的ですiです。

そしてis_constructible<A, int&&>::valueが真です。ここで、差分を別の方法を提示する

は極めてストリッピングタプル実装である:

struct D { D(int) {} D(const D&) = delete; }; 

template <typename T> 
struct Tuple { 
    Tuple(const T& t) 
    : T(t) 
    { } 

    template <typename U, 
#ifdef USE_OLD_RULES 
       typename = std::enable_if_t<std::is_convertible<U, T>::value> 
#else 
       typename = std::enable_if_t<std::is_constructible<T, U&&>::value> 
#endif 
       > 
    Tuple(U&& u) 
    : t(std::forward<U>(u)) 
    { } 

    T t; 
}; 

int main() 
{ 
    Tuple<D> t(12); 
} 

USE_OLD_RULESが定義されている場合、最初のコンストラクタは唯一の実行可能なコンストラクタとDであるので、したがって、コードがコンパイルされませんコピー不可能。それ以外の場合は、2番目のコンストラクタが最適な実行可能な候補であり、1つが適切に構成されています。


gcc 5.2もclang 3.6も実際にこの例を実際にコンパイルすることはできませんでした。だから、それよりも新しいコンパイラが必要になるか(gcc 6.0が動作する)、あるいは別のデザインを思いつくでしょう。

+0

私は 'std :: tuple td = std :: make_tuple(12);'もうまくいくと思っていますか? – Yakk

+1

は 'template 明示的なタプル(UTypes && ... args)ではありません;'コンストラクタはC++ 11で利用できますか? –

+0

@ Yakkいいえ、そのコンストラクタは '明示的に'です。しかし、 'std :: tuple td {std :: make_tuple(12)}'も、その論文の採択前にはうまくいきません。 – Barry

1

あなたの問題は、明示的にrvalue参照のタプルを要求し、rvalue参照がポインタから遠く離れていないことです。

だからauto q = std::tuple<A&&,A&&>(A{100},A{200});は...参照でタプルを構築し、それらに(右辺値)の参照を取り、2つのAオブジェクトを作成し、それが言われていても2つのダングリング参照

であなたを残して、一時的なオブジェクトを破壊します古い古いCとそのぶら下がっているポインタよりも安全であるとすれば、C++はプログラマーに間違ったプログラムを書くことができます。

とにかく、以下では(&なく& &の使用に注意してください)理にかなって:

int main() { 
    A a(100), b(100); // Ok, a and b will leave as long as main 
    auto q = tuple<A&, A&>(a, b); // ok, q contains references to a and b 
    ... 
    return 0; // Ok, q, a and b will be destroyed 
} 
関連する問題