web-dev-qa-db-ja.com

可変テンプレート引数ごとに1つのクラスメンバーを生成する

各テンプレート引数が内部計算で処理できる1つのタイプの値を表すテンプレートクラスがあります。値はboost :: anyとして渡されるため、テンプレートは(関数のオーバーロードの代わりに)必要であり、その型は実行前に明確ではありません。

正しい型に正しくキャストするには、次のように、可変個引数の型ごとにメンバーリストを用意します。

template<typename ...AcceptedTypes> // e.g. MyClass<T1, T2>
class MyClass {
    std::vector<T1> m_argumentsOfType1;
    std::vector<T2> m_argumentsOfType2; // ...
};

または、テンプレート引数の型をリストに保存して、RTTIの魔法を使って(?)しかし、それらをstd :: initializer_listメンバーに保存する方法も私には不明確です。

助けてくれてありがとう!

29
user1101674

すでに示唆したように、最良の方法はタプルを使用することです:

template<typename ...AcceptedTypes> // e.g. MyClass<T1, T2>
class MyClass {
    std::Tuple<std::vector<AcceptedTypes>...> vectors;
};

「フィールド」を乗算する唯一の方法は、フィールド名を魔法のように綴ることができないためです。別の重要なことは、それらへの名前付きアクセスを取得することかもしれません。あなたが達成しようとしているのは、nique型の複数のベクトルを持つことであるため、次の機能を使用して、値の型で正しいベクトルを「検索」できます。

template <class T1, class T2>
struct SameType
{
    static const bool value = false;
};

template<class T>
struct SameType<T, T>
{
    static const bool value = true;
};

template <typename... Types>
class MyClass
{
     public:
     typedef std::Tuple<vector<Types>...> vtype;
     vtype vectors;

     template<int N, typename T>
     struct VectorOfType: SameType<T,
        typename std::Tuple_element<N, vtype>::type::value_type>
     { };

     template <int N, class T, class Tuple,
              bool Match = false> // this =false is only for clarity
     struct MatchingField
     {
         static vector<T>& get(Tuple& tp)
         {
             // The "non-matching" version
             return MatchingField<N+1, T, Tuple,
                    VectorOfType<N+1, T>::value>::get(tp);
         }
     };

     template <int N, class T, class Tuple>
     struct MatchingField<N, T, Tuple, true>
     {
        static vector<T>& get(Tuple& tp)
        {
            return std::get<N>(tp);
        }
     };

     template <typename T>
     vector<T>& access()
     {
         return MatchingField<0, T, vtype,
                VectorOfType<0, T>::value>::get(vectors);
     }
};

ここにテストケースがありますので、試すことができます:

int main( int argc, char** argv )
{
    int twelf = 12.5;
    typedef reference_wrapper<int> rint;

    MyClass<float, rint> mc;
    vector<rint>& i = mc.access<rint>();

    i.Push_back(twelf);

    mc.access<float>().Push_back(10.5);

    cout << "Test:\n";
    cout << "floats: " << mc.access<float>()[0] << endl;
    cout << "ints: " << mc.access<rint>()[0] << endl;
    //mc.access<double>();

    return 0;
}

MyClassを特殊化するために渡した型のリストにない型を使用すると(このコメントアウトされたdoubleのアクセスを参照)、コンパイルエラーが発生しますが、可読性は低くなりますが、gccは少なくとも正しい場所を指します。問題を引き起こしており、少なくともそのようなエラーメッセージが問題の正しい原因を示唆しています-たとえば、ここでmc.access <double>()

 error: ‘value’ is not a member of ‘MyClass<float, int>::VectorOfType<2, double>’
18
Ethouris

タプルを使用しない別の解決策は、CRTPを使用してクラス階層を作成することです。ここで、各基本クラスはタイプの1つに特化しています。

#include <iostream>
#include <string>

template<class L, class... R> class My_class;

template<class L>
class My_class<L>
{
public:

protected:
  L get()
  {
    return val;
  }

  void set(const L new_val)
  {
    val = new_val;
  }

private:
  L val;
};

template<class L, class... R>
class My_class : public My_class<L>, public My_class<R...>
{
public:
  template<class T>
  T Get()
  {
    return this->My_class<T>::get();
  }

  template<class T>
  void Set(const T new_val)
  {
    this->My_class<T>::set(new_val);
  }
};

int main(int, char**)
{
  My_class<int, double, std::string> c;
  c.Set<int>(4);
  c.Set<double>(12.5);
  c.Set<std::string>("Hello World");

  std::cout << "int: " << c.Get<int>() << "\n";
  std::cout << "double: " << c.Get<double>() << "\n";
  std::cout << "string: " << c.Get<std::string>() << std::endl;

  return 0;
}
5
shay

Πάντα-ῥεῖのコメントで述べられているように、そのようなことを行う1つの方法は、タプルを使用することです。彼が(おそらくあなたをあなた自身から救うために)説明しなかったのは、それがどのように見えるかです。

次に例を示します。

using namespace std;

// define the abomination    
template<typename...Types>
struct thing
{
    thing(std::vector<Types>... args)
    : _x { std::move(args)... }
    {}

    void print()
    {
        do_print_vectors(std::index_sequence_for<Types...>());
    }

private:
    template<std::size_t... Is>
    void do_print_vectors(std::index_sequence<Is...>)
    {
        using swallow = int[];
        (void)swallow{0, (print_one(std::get<Is>(_x)), 0)...};
    }

    template<class Vector>
    void print_one(const Vector& v)
    {
        copy(begin(v), end(v), ostream_iterator<typename Vector::value_type>(cout, ","));
        cout << endl;
    }

private:
    Tuple<std::vector<Types>...> _x;
};


// test it
BOOST_AUTO_TEST_CASE(play_tuples)
{
    thing<int, double, string> t {
        { 1, 2, 3, },
        { 1.1, 2.2, 3.3 },
        { "one"s, "two"s, "three"s }
    };

    t.print();
}

予想される出力:

1,2,3,
1.1,2.2,3.3,
one,two,three,
5
Richard Hodges

以下は、boost::variantを使用した完全に効率的な実装ではありません。

template<typename ... Ts>
using variant_vector = boost::variant< std::vector<Ts>... >;

template<typename ...Ts>
struct MyClass {
  using var_vec = variant_vector<Ts...>;
  std::array<var_vec, sizeof...(Ts)> vecs;
};

型のリストの1つを保持できるバリアントベクトルを作成します。コンテンツを取得するには、boost::variantを使用する必要があります(つまり、コンテンツのタイプを知るか、ビジターを作成する)。

次に、これらのバリアントベクトルの配列をタイプごとに1つ格納します。

これで、クラスが1つのタイプのデータしか保持しない場合、配列を取り除き、タイプvar_vecのメンバーを1つだけ持つことができます。

各タイプの1つのベクトルが必要な理由がわかりません。各要素が任意のタイプの1つであるベクトルが必要であることがわかりました。上記のvector<variant<Ts...>>とは対照的に、これはvariant<vector<Ts>...>になります。

variant<Ts...>boost union-with-typeです。 anyboost smart -void*です。 optionalboostの有無です。

template<class...Ts>
boost::optional<boost::variant<Ts...>> to_variant( boost::any );

anyを受け取り、それをvariantTs...型のいずれかに変換しようと試み、成功した場合はそれを返します(そして空の関数を返します) optionalない場合)。

直感的な構文 P1858R1一般化されたパックの宣言と使用法 を使用して、この種の拡張を可能にする提案があります。メンバーを初期化して、インデックスでアクセスすることもできます。 using... Tuple_element = /*...*/を記述することで、構造化バインディングをサポートすることもできます。

template <typename... Ts>
class MyClass {
    std::vector<Ts>... elems;
public:
    using... Tuple_element = std::vector<Ts>;

    MyClass() = default;
    explicit MyClass(std::vector<Ts>... args) noexcept
        : elems(std::move(args))...
    {
    }

    template <std::size_t I>
        requires I < sizeof...(Ts)
    auto& get() noexcept
    {
        return elems...[I];
    }

    template <std::size_t I>
        requires I < sizeof...(Ts)
    const auto& get() const
    {
        return elems...[I];
    }

    // ...
};

その後、クラスは次のように使用できます。

using Vecs = MyClass<int, double>;

Vecs vecs{};
vecs.[0].resize(3, 42);

std::array<double, 4> arr{1.0, 2.0, 4.0, 8.0};
vecs.[1] = {arr.[:]};

// print the elements
// note the use of vecs.[:] and Vecs::[:]
(std::copy(vecs.[:].begin(), vecs.[:].end(),
           std::ostream_iterator<Vecs::[:]>{std::cout, ' '},
 std::cout << '\n'), ...);
0
L. F.