wake-up-neo.com

Generierung eines Klassenmitglieds pro variadic-Vorlagenargument

Ich habe eine Vorlagenklasse, in der jedes Vorlagenargument für einen Wert steht, den die interne Berechnung verarbeiten kann. Templates (statt Überladen von Funktionen) werden benötigt, da die Werte als boost :: any übergeben werden und ihre Typen vor der Laufzeit nicht eindeutig sind. 

Um richtig in die richtigen Typen umzuwandeln, würde ich gerne eine Mitgliederliste für jeden variadischen Argumenttyp haben, etwa wie folgt:

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

Oder ich möchte alternativ die Vorlagenargumenttypen in einer Liste speichern, um RTTI-Magie damit zu tun (?). Aber wie man sie in einem Mitglied von std :: initializer_list speichert, ist mir auch unklar.

Danke für jede Hilfe!

25
user1101674

Wie Sie bereits angedeutet haben, verwenden Sie am besten einen Tupel:

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

Dies ist die einzige Möglichkeit, die "Felder" zu multiplizieren, da Sie die Feldnamen nicht auf magische Weise buchstabieren können. Eine weitere wichtige Sache kann sein, dass Sie einen benannten Zugriff darauf haben. Ich vermute, dass Sie mehrere Vektoren mit unique -typen haben wollen, so dass Sie die folgende Möglichkeit haben, den richtigen Vektor anhand seines Wertetyps zu "suchen":

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);
     }
};

Hier ist der Testfall, damit Sie es ausprobieren können:

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;
}

Wenn Sie einen Typ verwenden, der nicht in der Liste der Typen enthalten ist, die Sie zur Spezialisierung von MyClass übergeben haben (siehe den auskommentierten Zugriff für double), wird ein Kompilierungsfehler angezeigt, der nicht zu lesbar ist hat das Problem verursacht, und zumindest eine solche Fehlermeldung weist auf die richtige Ursache des Problems hin - hier zum Beispiel, wenn Sie versucht haben, mc.access <double> () zu tun:

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

Eine Möglichkeit, dies zu tun, wie in dem Kommentar von πάντα-ῥεῖ erwähnt, ist die Verwendung eines Tuples. Was er nicht erklärt hat (wahrscheinlich, um Sie vor sich selbst zu retten), ist, wie das aussehen könnte.

Hier ist ein Beispiel:

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();
}

erwartete Ausgabe:

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

Eine alternative Lösung, die keine Tupel verwendet, ist die Verwendung von CRTP zum Erstellen einer Klassenhierarchie, wobei jede Basisklasse eine Spezialisierung für einen der Typen ist:

#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;
}
2
shay

Hier ist eine weniger effiziente Implementierung mit 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;
};

wir erstellen einen Variantenvektor, der eine Liste von Typen enthalten kann. Sie müssen boost::variant verwenden, um zu den Inhalten zu gelangen (was bedeutet, die Art des Inhalts zu kennen oder einen Besucher zu schreiben).

Wir speichern dann ein Array dieser varianten Vektoren, einen pro Typ.

Wenn Ihre Klasse immer nur einen Datentyp enthält, können Sie auf das Array verzichten und haben nur ein Element vom Typ var_vec.

Ich kann nicht verstehen, warum Sie einen Vektor von jedem Typ benötigen. Ich könnte einen Vektor wünschen, bei dem jedes Element von einem beliebigen Typ ist. Das wäre ein vector<variant<Ts...>>, im Gegensatz zum obigen variant<vector<Ts>...>.

variant<Ts...> ist der Union-with-Typ boost. any ist der boost smart -void*. optional ist das boost-dort-oder-nicht.

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

kann eine nützliche Funktion sein, die any verwendet und versucht, sie in einen der Ts...-Typen in variant zu konvertieren, und diese zurückgibt, wenn sie erfolgreich ist (und eine leere optional-Anweisung, falls nicht).