Вообщем, проблема такая. Есть некое API, которое пишет куда-то вектора данных разных типов, которые я ему даю. Что-то вроде такого:
template<class T>
class DataSet
{
public:
DataSet(const std::string& colname) {
m_name = colname;
}
std::vector<T> getValues() {
return m_values;
}
void addValue(const T& val) {
m_values.push_back(val);
}
void write(const std::vector<T>& values) {
// write out the vector
}
private:
std::string m_name;
std::vector<T> m_values;
};
Мне надо обработать тонны подобных векторов и записать их методом Dataset::write. Для примера:
std::vector<std::string> column_names = {"A", "B", "C"};
std::vector<int> intValues;
std::vector<float> floatValues;
std::vector<double> doubleValues;
DataSet<int> intDataset("A");
DataSet<float> floatDataset("B");
DataSet<double> doubleDataset("C");
/*add elements to intValues, floatValues, doubleValues*/
intDataset.write(intValues);
floatDataset.write(floatValues);
doubleDataset.write(doubleValues);
Проблема в том, что у меня этих веторов очень много, они разных типов. Их количество, имена и типы иногда меняются в спецификации, до этапа компилирования, и мне приходится их вручную менять в коде. Код получается очень некрасивый.
Хотелось бы что-то такое сделать, чтобы я мог где-то в одном месте определить их имена и типы, а потом создать вектора, DataSet'ы, вызвать write(...) в цикле. То есть если имена и типы поменяются, чтобы мне не надо было их менять вручную в 3х местах, и если у меня этих векторов 100 штук, чтобы не плодить 300 строк кода.
Как мне это элегантнее сделать?
Здравствуйте, RiNSpy, Вы писали:
RNS>Проблема в том, что у меня этих веторов очень много, они разных типов. Их количество, имена и типы иногда меняются в спецификации, до этапа компилирования, и мне приходится их вручную менять в коде. Код получается очень некрасивый.
RNS>Хотелось бы что-то такое сделать, чтобы я мог где-то в одном месте определить их имена и типы, а потом создать вектора, DataSet'ы, вызвать write(...) в цикле. То есть если имена и типы поменяются, чтобы мне не надо было их менять вручную в 3х местах, и если у меня этих векторов 100 штук, чтобы не плодить 300 строк кода.
RNS>Как мне это элегантнее сделать?
Например так:
struct Writer {
typedef std::function<void()> fn;
std::vector<fn> list;
void add(fn t) { list.push_back(t); }
void write() { for(auto x : list) x(); }
};
#define DECL_DATASET_FOR(var,ds,name) \
DataSet<decltype(var)::value_type> ds(name); \
column_names.push_back(name); \
writer.add([&]{ ds.write(var); });
...
std::vector<int> intValues;
std::vector<float> floatValues;
std::vector<double> doubleValues;
...
Writer writer;
std::vector<std::string> column_names;
DECL_DATASET_FOR(intValues,intDataset,"A")
DECL_DATASET_FOR(floatValues,floatDataset,"B")
DECL_DATASET_FOR(doubleValues,doubleDataset,"C")
// еще 297 шт
/*add elements to intValues, floatValues, doubleValues*/
writer.write();
Здравствуйте, RiNSpy, Вы писали:
RNS>Вообщем, проблема такая. Есть некое API, которое пишет куда-то вектора данных разных типов, которые я ему даю. Что-то вроде такого:
RNS>Мне надо обработать тонны подобных векторов и записать их методом Dataset::write. Для примера:
RNS>Проблема в том, что у меня этих веторов очень много, они разных типов. Их количество, имена и типы иногда меняются в спецификации, до этапа компилирования, и мне приходится их вручную менять в коде. Код получается очень некрасивый.
RNS>Хотелось бы что-то такое сделать, чтобы я мог где-то в одном месте определить их имена и типы, а потом создать вектора, DataSet'ы, вызвать write(...) в цикле. То есть если имена и типы поменяются, чтобы мне не надо было их менять вручную в 3х местах, и если у меня этих векторов 100 штук, чтобы не плодить 300 строк кода.
RNS>Как мне это элегантнее сделать?
Фактически задача сводится к тому, чтобы связать имена колонок с их типами, после этого все раскручивается на шаблонах как по маслу.
Полный текст примера:
https://ideone.com/lZjCQK
Связка имен с типами делается при помощи введения мнемонических имен (идентификаторов), совпадающих с именами колонок (но только без кавычек). Эти идентификаторы будут потом использоваться как параметры шаблонов, служаших для доступа к типам, функциям и переменным. Сделать это можно при помощи нехитрого макроса.
template <typename> struct ColumnTraits;
#define DEFINE_COLUMN(name, type) \
class name; \
template<> \
struct ColumnTraits<name> \
{ \
using ColumnType = type; \
static constexpr const char* column_name = #name; \
};
DEFINE_COLUMN(A, int)
DEFINE_COLUMN(B, float)
DEFINE_COLUMN(C, double)
DEFINE_COLUMN(D, int)
DEFINE_COLUMN(E, float)
DEFINE_COLUMN(F, double)
Собственно это и есть ядро метаданных. Для доступа к ним пишется унифицированный набор шаблонных аксессоров:
template <typename Column>
constexpr const char* column_name = ColumnTraits<Column>::column_name;
template <typename Column>
using ColumnType = typename ColumnTraits<Column>::ColumnType;
template <typename Column>
using VectorType = std::vector<ColumnType<Column>>;
template <typename Column>
using DataSetType = DataSet<ColumnType<Column>>;
Теперь получить тип DataSet, соответствующий колонке "B", можно при помощи простой конструкции DataSetType<B>. Тип соответствующего вектора — VectorType<B>. Имя колонки — column_name<B>.
Помимо шаблонов типов можно также определить шаблоны глобальных переменных, если они нужны:
// You can even define templates for flobal variables, if it's necessary:
template <typename Column>
VectorType<Column> global_values;
template <typename Column>
DataSetType<Column> global_dataset(column_name<Column>);
После этого уже не нужно определять индивидуально для каждой колонки: intValues, floatValues, doubleValues, intDataset, floatDataset, doubleDataset... мы все уже сделали — парой шаблоннов переменных! И доступиться к ним теперь можно при помощи простых выражений: global_dataset<A>, global_values<B>...
Теперь можно предоставить дополнительные утилиты для работы с этими глобальными переменными:
// Yoy can provide utility for global writhing by specified types:
template <typename...Columns>
void write_values(const VectorType<Columns>&...values)
{
std::initializer_list<int>{ (global_dataset<Columns>.write(values), 0)... };
}
template <typename...Columns>
void write_global_values()
{
write_values<Columns...>(global_values<Columns>...);
}
Теперь заполнить необходимые dataset-ы, все или выборочно, можно тоже простыми выражениями:
global_dataset<D>.write({1, 2, 3, 4, 5});
global_dataset<B>.write(global_values<B>);
write_values<A, C>({1, 2}, {3.14, 0.1});
write_global_values<F, A, C, E>();
Я нигде не использовал column_names потому, что не понял, зачем они нужны и какой у них скоуп. Их формирование без труда можно впихнуть внутрь макроса DEFINE_COLUMN, если хочется.
Полный текст примера здесь:
https://ideone.com/lZjCQK
P.S. В этом примере не будет работать автоматическое выведение типов для шаблонной функции write_values. По понятным причинам. Это можно победить, при необходимости, но сейчас я этого не делаю, чтобы не усложнять пример.