Подтвердить что ты не робот

Генерирование одного члена класса на переменный аргумент шаблона

У меня есть класс шаблона, где каждый аргумент шаблона обозначает один тип значения, который может обрабатывать внутреннее вычисление. Шаблоны (вместо перегрузки функций) необходимы, потому что значения передаются как 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 также неясно мне.

Спасибо за любую помощь!

4b9b3361

Ответ 1

Как вы уже намекнули, лучший способ - использовать кортеж:

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

Это единственный способ умножить "поля", потому что вы не можете волшебным образом записать имена полей. Еще одна важная вещь может заключаться в том, чтобы получить к ним некоторый именованный доступ. Я предполагаю, что то, что вы пытаетесь достичь, - это иметь несколько векторов с уникальными типами, чтобы у вас была следующая возможность "искать" правильный вектор по его типу значения:

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>

Ответ 2

Один из способов сделать такую ​​вещь, как упоминается в πάντα-ῥεῖ, - это использовать кортеж. То, что он не объяснял (возможно, чтобы спасти вас от себя), как это может выглядеть.

Вот пример:

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,

Ответ 3

Альтернативное решение, которое не использует кортежи, заключается в использовании CRTP для создания иерархии классов, где каждый базовый класс является специализацией для одного из типов:

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

Ответ 4

Ниже приведена менее эффективная реализация с использованием 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;
};

мы создаем вектор-вариант, который может содержать один из списка типов в нем. Вы должны использовать boost::variant для получения содержимого (что означает знание типа содержимого или запись посетителя).

Затем мы сохраняем массив этих вариантов векторов, по одному на тип.

Теперь, если ваш класс только когда-либо хранит один тип данных, вы можете избавиться от массива и просто иметь один член типа var_vec.

Я не понимаю, почему вам нужен один вектор каждого типа. Я мог видеть, что вам нужен вектор, в котором каждый элемент является одним из любого типа. Это будет vector<variant<Ts...>>, в отличие от выше variant<vector<Ts>...>.

variant<Ts...> является boost union-with-type. any - это boost smart- void*. optional - это boost там или не.

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

может быть полезной функцией, которая принимает any и пытается преобразовать ее в любой из Ts... типов в variant и возвращает ее, если она преуспевает (и возвращает пустой optional, если не).