111

要素をから移動することはできstd::initializer_list<T>ますか?

#include <initializer_list>
#include <utility>

template<typename T>
void foo(std::initializer_list<T> list)
{
    for (auto it = list.begin(); it != list.end(); ++it)
    {
        bar(std::move(*it));   // kosher?
    }
}

特別なコンパイラの注意が必要であり、C ++標準ライブラリの通常のコンテナのような値のセマンティクスがないためstd::intializer_list<T>、申し訳ありませんが、質問するよりも安全です。

4

8 に答える 8

108

いいえ、それは意図したとおりには機能しません。あなたはまだコピーを取得します。initializer_list私はこれにかなり驚いています。それは、一時的なものがなくなるまで一連の一時的なものを保持するために存在していたと思っていたからmoveです。

beginそしてreturnのend場合、コード内の結果は—不変の右辺値参照です。このような式は、意味のある形から移動することはできません。右辺値は定数左辺値参照にバインドされるため、型の関数パラメーターにバインドされますが、コピーのセマンティクスは引き続き表示されます。initializer_listconst T *moveT const &&T const &

おそらくこれの理由は、コンパイラが静的に初期化された定数を作成することを選択できるためですが、その型を作成するか、コンパイラの裁量でinitializer_list作成する方がクリーンであると思われるため、ユーザーは、または可変であるかどうかがわかりません。とからの結果。しかし、それは私の直感です。おそらく、私が間違っているのには十分な理由があります。initializer_listconst initializer_listconstbeginend

更新:移動専用タイプをサポートするためのISO提案を作成しました。initializer_listこれは最初のドラフトであり、まだどこにも実装されていませんが、問題の詳細な分析のためにそれを見ることができます。

于 2011-11-19T09:38:59.053 に答える
23
bar(std::move(*it));   // kosher?

あなたが意図した方法ではありません。オブジェクトを移動することはできませんconst。そして、その要素へのアクセスstd::initializer_listのみを提供します。constしたがって、のタイプはitですconst T *

呼び出しようstd::move(*it)とすると、l値のみになります。IE:コピー。

std::initializer_list静的メモリを参照します。それがクラスの目的です。移動は静的メモリを変更することを意味するため、静的メモリから移動することはできません。そこからのみコピーできます。

于 2011-11-19T16:29:19.897 に答える
3

list.begin()タイプconst T *があり、定数オブジェクトから移動する方法がないため、これは前述のようには機能しません。言語設計者はおそらく、初期化子リストにたとえば文字列定数を含めることができるようにするためにそうしましたが、そこから移動するのは不適切です。

ただし、初期化子リストに右辺値式が含まれていることがわかっている場合(または、ユーザーにそれらを強制的に記述させたい場合)、それを機能させるトリックがあります(Sumantの回答に触発されましたこれですが、解決策はそれよりもはるかに簡単です)。初期化子リストに格納されている要素は、T値ではなく、をカプセル化する値である必要がありますT&&。そうすれば、それらの値自体がconst修飾されている場合でも、変更可能な右辺値を取得できます。

template<typename T>
  class rref_capture
{
  T* ptr;
public:
  rref_capture(T&& x) : ptr(&x) {}
  operator T&& () const { return std::move(*ptr); } // restitute rvalue ref
};

initializer_list<T>ここで、引数を宣言する代わりに、引数を宣言しinitializer_list<rref_capture<T> >ます。これは、移動セマンティクスのみが定義されているスマートポインターのベクトルを含む具体的な例ですstd::unique_ptr<int>(したがって、これらのオブジェクト自体を初期化子リストに格納することはできません)。それでも、以下の初期化子リストは問題なくコンパイルされます。

#include <memory>
#include <initializer_list>
class uptr_vec
{
  typedef std::unique_ptr<int> uptr; // move only type
  std::vector<uptr> data;
public:
  uptr_vec(uptr_vec&& v) : data(std::move(v.data)) {}
  uptr_vec(std::initializer_list<rref_capture<uptr> > l)
    : data(l.begin(),l.end())
  {}
  uptr_vec& operator=(const uptr_vec&) = delete;
  int operator[] (size_t index) const { return *data[index]; }
};

int main()
{
  std::unique_ptr<int> a(new int(3)), b(new int(1)),c(new int(4));
  uptr_vec v { std::move(a), std::move(b), std::move(c) };
  std::cout << v[0] << "," << v[1] << "," << v[2] << std::endl;
}

1つの質問に答えが必要です。初期化子リストの要素が真のprvalues(この例ではxvalues)である必要がある場合、言語は、対応する一時的なものの存続期間がそれらが使用されるポイントまで延長されることを保証しますか?率直に言って、私は、規格の関連するセクション8.5がこの問題にまったく対処していないと思います。ただし、1.9:10を読むと、すべての場合に関連する完全式には初期化子リストの使用が含まれているように見えるので、右辺値参照がぶら下がる危険はないと思います。

于 2014-07-07T11:32:50.293 に答える
2

回避策の合理的な出発点を提供することは有益かもしれないと思いました。

インラインでコメントします。

#include <memory>
#include <vector>
#include <array>
#include <type_traits>
#include <algorithm>
#include <iterator>

template<class Array> struct maker;

// a maker which makes a std::vector
template<class T, class A>
struct maker<std::vector<T, A>>
{
  using result_type = std::vector<T, A>;

  template<class...Ts>
  auto operator()(Ts&&...ts) const -> result_type
  {
    result_type result;
    result.reserve(sizeof...(Ts));
    using expand = int[];
    void(expand {
      0,
      (result.push_back(std::forward<Ts>(ts)),0)...
    });

    return result;
  }
};

// a maker which makes std::array
template<class T, std::size_t N>
struct maker<std::array<T, N>>
{
  using result_type = std::array<T, N>;

  template<class...Ts>
  auto operator()(Ts&&...ts) const
  {
    return result_type { std::forward<Ts>(ts)... };
  }

};

//
// delegation function which selects the correct maker
//
template<class Array, class...Ts>
auto make(Ts&&...ts)
{
  auto m = maker<Array>();
  return m(std::forward<Ts>(ts)...);
}

// vectors and arrays of non-copyable types
using vt = std::vector<std::unique_ptr<int>>;
using at = std::array<std::unique_ptr<int>,2>;


int main(){
    // build an array, using make<> for consistency
    auto a = make<at>(std::make_unique<int>(10), std::make_unique<int>(20));

    // build a vector, using make<> because an initializer_list requires a copyable type  
    auto v = make<vt>(std::make_unique<int>(10), std::make_unique<int>(20));
}
于 2017-06-01T12:36:28.427 に答える
1

を使用する代わりにstd::initializer_list<T>、引数を配列右辺値参照として宣言できます。

template <typename T>
void bar(T &&value);

template <typename T, size_t N>
void foo(T (&&list)[N] ) {
   std::for_each(std::make_move_iterator(std::begin(list)),
                 std::make_move_iterator(std::end(list)),
                 &bar);
}

void baz() {
   foo({std::make_unique<int>(0), std::make_unique<int>(1)});
}

https://gcc.godbolt.org/z/2uNxv6を使用した例を参照してstd::unique_ptr<int>ください

于 2020-06-02T11:42:06.973 に答える
0

すでに回答されているように、現在の規格では許可されていないようです。初期化子リストを取得する代わりに関数を可変個引数として定義することにより、同様のことを実現するための別の回避策を次に示します。

#include <vector>
#include <utility>

// begin helper functions

template <typename T>
void add_to_vector(std::vector<T>* vec) {}

template <typename T, typename... Args>
void add_to_vector(std::vector<T>* vec, T&& car, Args&&... cdr) {
  vec->push_back(std::forward<T>(car));
  add_to_vector(vec, std::forward<Args>(cdr)...);
}

template <typename T, typename... Args>
std::vector<T> make_vector(Args&&... args) {
  std::vector<T> result;
  add_to_vector(&result, std::forward<Args>(args)...);
  return result;
}

// end helper functions

struct S {
  S(int) {}
  S(S&&) {}
};

void bar(S&& s) {}

template <typename T, typename... Args>
void foo(Args&&... args) {
  std::vector<T> args_vec = make_vector<T>(std::forward<Args>(args)...);
  for (auto& arg : args_vec) {
    bar(std::move(arg));
  }
}

int main() {
  foo<S>(S(1), S(2), S(3));
  return 0;
}

可変個引数テンプレートは、initializer_listとは異なり、r値参照を適切に処理できます。

このサンプルコードでは、一連の小さなヘルパー関数を使用して可変個引数をベクトルに変換し、元のコードと同様にしました。ただし、もちろん、代わりに可変個引数テンプレートを使用して再帰関数を直接作成することもできます。

于 2017-03-26T04:47:18.290 に答える
0

要素を移動する意図をマークするタグとして機能するラッパークラスを利用する、はるかに単純な実装があります。これはコンパイル時のコストです。

ラッパークラスは、使用方法std::moveで使用するように設計されており、に置き換えるだけですstd::movemove_wrapper、これにはC++17が必要です。古い仕様の場合は、追加のビルダーメソッドを使用できます。

initializer_list内部でラッパークラスを受け入れ、それに応じて要素を移動するビルダーメソッド/コンストラクターを作成する必要があります。

一部の要素を移動する代わりにコピーする必要がある場合は、に渡す前にコピーを作成してinitializer_listください。

コードは自己文書化する必要があります。

#include <iostream>
#include <vector>
#include <initializer_list>

using namespace std;

template <typename T>
struct move_wrapper {
    T && t;

    move_wrapper(T && t) : t(move(t)) { // since it's just a wrapper for rvalues
    }

    explicit move_wrapper(T & t) : t(move(t)) { // acts as std::move
    }
};

struct Foo {
    int x;

    Foo(int x) : x(x) {
        cout << "Foo(" << x << ")\n";
    }

    Foo(Foo const & other) : x(other.x) {
        cout << "copy Foo(" << x << ")\n";
    }

    Foo(Foo && other) : x(other.x) {
        cout << "move Foo(" << x << ")\n";
    }
};

template <typename T>
struct Vec {
    vector<T> v;

    Vec(initializer_list<T> il) : v(il) {
    }

    Vec(initializer_list<move_wrapper<T>> il) {
        v.reserve(il.size());
        for (move_wrapper<T> const & w : il) {
            v.emplace_back(move(w.t));
        }
    }
};

int main() {
    Foo x{1}; // Foo(1)
    Foo y{2}; // Foo(2)

    Vec<Foo> v{Foo{3}, move_wrapper(x), Foo{y}}; // I want y to be copied
    // Foo(3)
    // copy Foo(2)
    // move Foo(3)
    // move Foo(1)
    // move Foo(2)
}
于 2019-07-10T17:07:34.783 に答える
-1

cpptruthsin<T>で説明されているイディオムについて考えてみます。アイデアは、実行時に左辺値/右辺値を決定してから、moveまたはcopy-constructionを呼び出すことです。initializer_listによって提供される標準インターフェースがconst参照であっても、右辺値/左辺値を検出します。in<T>

于 2013-09-18T18:27:21.547 に答える