web-dev-qa-db-ja.com

可変長テンプレートコンストラクターの作成

最近、私は this の質問をしましたが、今それを拡張したいと思います。私は次のクラスを書きました:

template <class T>
class X{
public:
    vector<T> v;
    template <class T>
    X(T n) {
        v.Push_back(n);
    }
    template <class T, class... T2>
    X(T n, T2... rest) {
        v.Push_back(n);
        X(rest...);
    }
};

を使用してオブジェクトを作成する場合

X<int> obj(1, 2, 3);  // obj.v containts only 1

ベクトルには最初の値のみが含まれ、他の値は含まれません。コンストラクターが3回呼び出されることを確認しました。そのため、おそらく一時オブジェクトを作成し、残りの引数でベクターを埋めています。この問題を解決するにはどうすればよいですか?

20
Tracer

まず、あなたのコードは私のためにコンパイルされません。

_main.cpp:7:15: error: declaration of ‘class T’
     template <class T>
               ^
main.cpp:3:11: error:  shadows template parm ‘class T’
 template <class T>
           ^
_

外側のものをUに変更しました。

_template <class U>
class X{
public:
    vector<U> v;
    template <class T>
    X(T n) {
        v.Push_back(n);
    }
    template <class T, class... T2>
    X(T n, T2... rest) {
        v.Push_back(n);
        X(rest...);
    }
};
_

これにより、質問の詳細で指定した問題が発生することは正しいです...

_X<int> obj(1, 2, 3);  // obj.v containts only 1
_

これは、コンストラクターの最後にあるステートメントX(rest...)が、同じオブジェクトの初期化を継続するためにコンストラクターを再帰的に呼び出さないためです。 newXオブジェクトを作成して、破棄します。コンストラクタのbodyが実行を開始すると、同じオブジェクトで別のコンストラクタを呼び出すことはできなくなります。 ctor-initializerで委任が発生する必要があります。したがって、たとえば、これを行うことができます:

_template <class T, class... T2>
X(T n, T2... rest): X(rest...) {
    v.insert(v.begin(), n);
}
_

ただし、ベクトルの先頭に挿入するのは効率的ではないため、それは残念です。

_std::initializer_list<T>_引数を取る方が良いです。これは_std::vector_自体が行うことです。

_X(std::initializer_list<U> il): v(il) {}
// ...
X<int> obj {1, 2, 3};
_
30
Brian

ブライアンの答えに完全に同意しますが、正しいアプローチが別の場合(つまり、initialize_listを使用する場合)でも、この場合の可変引数テンプレートの再帰は、単に空のパックは有効なテンプレートパラメーターパックであるため、可変パックテンプレートコンストラクターは空のパックで最後に1回呼び出されます。これにより、デフォルトのctorを呼び出そうとすることになります。

IOWでは、次のように機能し、IMOがよりクリーンになります。

template <class T>
struct X
{
    std::vector<T> v;
    X() = default; //Terminating recursion

    template <class U, class... Ts>
    X(U n, Ts... rest)  : X(rest...) { .. the recursive work ..}
};

繰り返しになりますが、ここではテンプレートと再帰が正しいことだとは言っていません。テンプレートが必要であり、それらを使用するより簡単な方法があることを指摘しています。

5
abigagli

そもそも再帰の必要はありません-
「一時配列」イディオムを使用して書くことができます

template <class... E>
X(E&&... e) {
    int temp[] = {(v.Push_back(e), 0)...};
}

このアプローチの適切な(そしてより複雑な)バージョンは次のようになります。

template <class... E>
X(E&&... e) {
    (void)std::initializer_list<int>{(v.Push_back(std::forward<E>(e)), void(), 0)...};
}

ライブバージョン

C++の次のバージョンにはおそらく Fold Expressions があることに注意してください
(v.Push_back(e), ...);

4
Abyx
template <class T, class... Rest>
  X(T n, Rest... rest) {
   add(rest...);
 }

//Just another member function
template<class T>
  void add(T n) {
    v.Push_back(n);
 }
  template<class T, class ... Rest>
   void add(T n, Rest... rest) {
     v.Push_back(n);
     add(rest...);

 }
1
fengzi