web-dev-qa-db-ja.com

vector <T>をinitializer_list <T>に変換します

誰もがstd::vectorからstd::initializer_listを作成しますが、他の方法はどうですか?

例えば。 std::initializer_listをパラメーターとして使用する場合:

void someThing(std::initializer_list<int> items)
{
...
}

リテラルリストの代わりにvector<T>にアイテムがある場合があります。

std::vector<int> v;
// populate v with values
someThing(v); // boom! No viable conversion etc.

より一般的な質問は、stl::initializer_listだけでなく、反復可能なSTLからstd::vectorを作成する方法です。

53
Fil

動作しているように見えたが、initializer_listsがローカルスコープの値のコピーへの参照として扱われるため、残念ながらメモリアクセス違反を引き起こした方法を投稿しました。

これが代替案です。パラメータパックでカウントされるアイテムの可能な数ごとに、個別の関数と個別の静的初期化子リストが生成されます。これはスレッドセーフではなく、const_cast(非常に悪いと見なされます)を使用して静的なinitializer_listメモリに書き込みます。ただし、gccとclangの両方で正常に動作します。

何らかの理由でこの問題を解決する必要があり、他のオプションがない場合は、このハックを試すことができます。

#include <initializer_list>
#include <iostream>
#include <stdexcept>
#include <type_traits>
#include <vector>

namespace __range_to_initializer_list {

    constexpr size_t DEFAULT_MAX_LENGTH = 128;

    template <typename V> struct backingValue { static V value; };
    template <typename V> V backingValue<V>::value;

    template <typename V, typename... Vcount> struct backingList { static std::initializer_list<V> list; };
    template <typename V, typename... Vcount>
    std::initializer_list<V> backingList<V, Vcount...>::list = {(Vcount)backingValue<V>::value...};

    template <size_t maxLength, typename It, typename V = typename It::value_type, typename... Vcount>
    static typename std::enable_if< sizeof...(Vcount) >= maxLength,
    std::initializer_list<V> >::type generate_n(It begin, It end, It current)
    {
        throw std::length_error("More than maxLength elements in range.");
    }

    template <size_t maxLength = DEFAULT_MAX_LENGTH, typename It, typename V = typename It::value_type, typename... Vcount>
    static typename std::enable_if< sizeof...(Vcount) < maxLength,
    std::initializer_list<V> >::type generate_n(It begin, It end, It current)
    {
        if (current != end)
            return generate_n<maxLength, It, V, V, Vcount...>(begin, end, ++current);

        current = begin;
        for (auto it = backingList<V,Vcount...>::list.begin();
             it != backingList<V,Vcount...>::list.end();
             ++current, ++it)
            *const_cast<V*>(&*it) = *current;

        return backingList<V,Vcount...>::list;
    }

}

template <typename It>
std::initializer_list<typename It::value_type> range_to_initializer_list(It begin, It end)
{
    return __range_to_initializer_list::generate_n(begin, end, begin);
}

int main()
{
    std::vector<int> vec = {1,2,3,4,5,6,7,8,9,10};
    std::initializer_list<int> list = range_to_initializer_list(vec.begin(), vec.end());
    for (int i : list)
        std::cout << i << std::endl;
    return 0;
}
2
fuzzyTew

答えは「いいえ」です。それはできません。

タイプstd::initializer_list<T>のオブジェクトは、タイプTのオブジェクトの配列へのアクセスを提供する軽量プロキシオブジェクトです。std::initializer_listオブジェクトは自動構築の場合:

  • リストの初期化では、関数呼び出しリストの初期化と割り当て式を含むブレース初期化リストが使用されます(コンストラクタ初期化リストと混同しないでください)
  • braced-init-listは、範囲指定されたforループを含むautoにバインドされます

ライブラリのサポートに関する限り、std::initializer_listには空のリストを作成するデフォルトのコンストラクターのみがあり、その反復子は定数です。 Push_back()メンバーがないため、適用できません。 std::copyイテレータアダプタを使用してstd::back_inserterを埋めますが、そのようなイテレータを直接割り当てることもできません。

#include <algorithm>
#include <initializer_list>
#include <iterator>
#include <vector>

int main() 
{
    auto v = std::vector<int> { 1, 2 };
    std::initializer_list<int> i;
    auto it = std::begin(i);
    *it = begin(v); // error: read-only variable is not assignable
}

Live Example

標準コンテナーを見ると、コンストラクター/挿入子でstd::initializer_listを受け入れることに加えて、すべてイテレーターペアを取るコンストラクター/挿入子があり、実装は対応するイテレーターペア関数にinitializer_list関数を委任する可能性があります。例えば。 libc ++のstd::vector<T>::insert関数 は、この単純な1行です。

 iterator insert(const_iterator __position, initializer_list<value_type> __il)
        {return insert(__position, __il.begin(), __il.end());}

同様の行に沿ってコードを変更する必要があります。

void someThing(std::initializer_list<int> items)
{
    someThing(items.begin(), items.end()); // delegate
}

template<class It>
void someThing(It first, It last)
{
    for (auto it = first, it != last; ++it) // do your thing
}

リテラルリストではなくベクターにアイテムがある場合:

std::vector<int> v = { 1, 2 };
auto i = { 1, 2 };
someThing(begin(v), end(v)); // OK
someThing(i); // also OK
someThing({1, 2}); // even better
28
TemplateRex

どうやら いいえ、不可能です 。そのようなコンストラクターはありません(そして正当な理由があると思います)、_std::initializer_list_は奇妙な生き物です。

代わりにできることは、someThing()を変更して、イテレーターのペアを受け入れることです。その方法で、その関数の署名を変更できる場合(サードパーティのライブラリなどにない場合)、必要なものを取得できます。

7
Ali

はい、これを行うことはできますが、実行する必要はありません。実行する方法は非常にばかげているためです。

まず、リストの最大長を決定します。 size_tは無制限ではないため、最大長が必要です。理想的には、10などのより良い(小さい)ものを見つけます。

次に、実行時整数を受け取り、それをコンパイル時整数にマップし、そのコンパイル時整数でテンプレートクラスまたは関数を呼び出すマジックスイッチコードを記述します。このようなコードには最大整数サイズが必要です-上記の最大長を使用してください。

ここで、マジックがベクターのサイズをコンパイル時間の長さに切り替えます。

0からlength-1の整数のコンパイル時シーケンスを作成します。 initializer_list[]を呼び出すたびに、そのシーケンスをstd::vector構造でアンパックします。結果のinitializer_listで関数を呼び出します。

上記はトリッキーでばかげており、ほとんどのコンパイラーはそれを爆破します。私の合法性が不確かな1つのステップがあります-initializer_listの構築は、可変引数の展開を行うための合法的なスポットですか?

マジックスイッチの例を次に示します。 コンパイル時ストラテジーの作成場所と使用場所を分離できますか?

インデックス、またはシーケンス、トリックの例を次に示します。 Tupleからのコンストラクター引数

この投稿は理論的にのみ興味があるはずです。実際、これはこの問題を解決する本当にばかげた方法だからです。

N ^ 2の作業を行わずに、任意の反復可能オブジェクトでそれを行うのは困難です。しかし、上記はすでに十分にばかげているので、任意の反復可能なバージョンはもっとばかげているでしょう...初期化子リストへのさまざまな引数の評価?)

ベクトルの場合、ポインター演算を使用できます。

Foo( std::initializer_list< _Type >( aVector.data(), aVector.data() + aVector.size() ) ) ;

これを汎用関数でラップできます。

template< typename _Type >
auto    InitList( const _Type * begin, size_t size )
{
  return std::initializer_list< _Type >( begin, begin + size ) ;
}

このような関数を呼び出します。

Foo( InitList( aVector.data(), aVector.size() ) ) ;
1
QBziZ

コピーを気にしないのであれば、このようなことがうまくいくと思います:

template<class Iterator>
using iterator_init_list = std::initializer_list<typename std::iterator_traits<Iterator>::value_type>;

template<class Iterator, class... Ts>
iterator_init_list<Iterator> to_initializer_list(Iterator start, Iterator last, Ts... xs)
{
    if (start == last) return iterator_init_list<Iterator>{xs...};
    else return to_initializer_list(start+1, last, xs..., *start);
}
0
Paul Fultz II