У меня есть список «идентификаторов» объекта (длинный список перечисления, с уникальным значением для «идентификатора»):
enum Identifier {
Enum0, // an identifier for a bool value
Enum1, // ... for a float value
Enum2, // ... for an int value
// etc.
};
Я хочу сохранить коллекцию объектов Value, связанных с этими идентификаторами. Эти объекты содержат одно значение, но это может быть целое число, число с плавающей запятой, логическое значение или какой-либо другой (простой) тип. Это в контексте управления набором значений конфигурации в системе. Позже я планирую расширить эти типы значений для поддержки проверки внутреннего значения и связать некоторые значения с другими значениями.
Однако я хочу использовать шаблоны для этих классов значений, потому что я хочу написать операции над этими значениями в общем. Если бы я использовал наследование, у меня был бы BaseValue, а затем производные IntValue, FloatValue и т. Д. Из BaseValue. Вместо этого у меня есть значение, ценность и т. Д.
Но я также хочу хранить механизм доступа к каждому из этих значений в одной коллекции. Я хочу, чтобы один класс создал все из них и сохранил их в коллекции. Если бы я использовал наследование, я мог бы использовать вектор указателей на BaseValue. Но поскольку я использую шаблоны, эти классы не имеют полиморфной связи друг с другом.
Поэтому я подумал о том, чтобы сделать их основанными на (пустом?) Абстрактном базовом классе, который не параметризируются:
class BaseParameter {
};
template<typename T>
class Parameter : public BaseParameter {
public:
explicit Parameter(T val) : val_(val) {}
void set(ParameterSource src) { val_ = extract<T>(src); }
T get() { return val_; };
private:
T val_;
};
Обратите внимание, что функция-член ‘set’ принимает «ParameterSource», который является источником значения, которое «интерпретируется» определенными функциями «to_type». Это функция API вне моего контроля — я должен сам интерпретировать тип, учитывая, что я знаю, что это за тип, заданный ниже. Вот что делает экстракт — он специализирован для различных типов T, таких как float, int, bool.
Затем я могу добавить их в std :: vector следующим образом:
std::vector<BaseParameter *> vec(10);
vec[Enum0] = new Parameter<bool>(true); // this is where I state that it's a 'bool'
vec[Enum1] = new Parameter<float>(0.5); // ... or a float ...
vec[Enum2] = new Parameter<int>(42); // ... or an int ...
Я знаю, что, вероятно, мне следует использовать unique_ptr, но сейчас я просто пытаюсь заставить это работать. Пока, похоже, это работает нормально. Но я настороженно отношусь к этому, потому что я не уверен, что полный тип созданных шаблонов будет сохранен во время выполнения.
Позже я хочу проиндексировать ‘vec’ по произвольному значению перечисления, получить параметр и вызвать для него функцию-член:
void set_via_source(Identifier id, ParameterSource source) {
// if id is in range...
vec[id]->set(source);
}
И другой код, который использует эти значения конфигурации (и, следовательно, знает тип), может получить к ним доступ с помощью:
int foo = vec[Enum2]->get() * 7;
Казалось, это работает, большую часть времени. Это компилируется. У меня были странные сбои, которые я не могу объяснить, которые также приводят к сбоям в отладчике. Но я очень подозрительно отношусь к этому, потому что я не знаю, способен ли C ++ определить реальный тип объекта, на который указывает указатель (включая параметризованный тип), потому что базовый класс сам не параметризован.
К сожалению, мне кажется, что если я параметризирую базовый класс, то я, по сути, удаляю общность между этими классами Value, которая позволяет хранить их в одном контейнере.
Я посмотрел на boost :: any, чтобы увидеть, может ли это помочь, но я не уверен, что это применимо в этом случае.
На более высоком уровне я пытаюсь подключить обширную коллекцию элементов конфигурации из внешнего источника (через API), которая доставляет значения различного типа в зависимости от элемента, и сохранять их локально, чтобы остальная часть моего код может легко получить к ним доступ, как если бы они были простыми членами данных. Я также хочу избежать написания гигантского оператора switch (потому что это сработает).
Может ли это помочь мне с Type Erasure?
Если вы знаете во время компиляции тип, связанный с каждым перечислением, вы можете сделать это «легко» с boost::variant
и без стирания типа или даже наследования. (редактировать: Первое решение использует множество функций C ++ 11. В конце я поставил менее автоматическое, но совместимое с C ++ 03 решение.)
#include <string>
#include <vector>
#include <boost/variant.hpp>
#include <boost/variant/get.hpp>
// Here's how you define your enums, and what they represent:
enum class ParameterId {
is_elephant = 0,
caloric_intake,
legs,
name,
// ...
count_
};
template<ParameterId> struct ConfigTraits;
// Definition of type of each enum
template<> struct ConfigTraits<ParameterId::is_elephant> {
using type = bool;
};
template<> struct ConfigTraits<ParameterId::caloric_intake> {
using type = double;
};
template<> struct ConfigTraits<ParameterId::legs> {
using type = int;
};
template<> struct ConfigTraits<ParameterId::name> {
using type = std::string;
};
// ...
// Here's the stuff that makes it work.
class Parameters {
private:
// Quick and dirty uniquifier, just to show that it's possible
template<typename...T> struct TypeList {
using variant = boost::variant<T...>;
};
template<typename TL, typename T> struct TypeListHas;
template<typename Head, typename...Rest, typename T>
struct TypeListHas<TypeList<Head, Rest...>, T>
: TypeListHas<TypeList<Rest...>, T> {
};
template<typename Head, typename...Rest>
struct TypeListHas<TypeList<Head, Rest...>, Head> {
static const bool value = true;
};
template<typename T> struct TypeListHas<TypeList<>, T> {
static const bool value = false;
};
template<typename TL, typename T, bool B> struct TypeListMaybeAdd;
template<typename TL, typename T> struct TypeListMaybeAdd<TL, T, false> {
using type = TL;
};
template<typename...Ts, typename T>
struct TypeListMaybeAdd<TypeList<Ts...>, T, true> {
using type = TypeList<Ts..., T>;
};
template<typename TL, typename T> struct TypeListAdd
: TypeListMaybeAdd<TL, T, !TypeListHas<TL, T>::value> {
};
template<typename TL, int I> struct CollectTypes
: CollectTypes<typename TypeListAdd<TL,
typename ConfigTraits<ParameterId(I)>::type
>::type, I - 1> {
};
template<typename TL> struct CollectTypes<TL, 0> {
using type = typename TypeListAdd<TL,
typename ConfigTraits<ParameterId(0)>::type
>::type::variant;
};
public:
using value_type =
typename CollectTypes<TypeList<>, int(ParameterId::count_) - 1>::type;
template<ParameterId pid>
using param_type = typename ConfigTraits<pid>::type;
// It would be better to not initialize all the values twice, but this
// was easier.
Parameters() : values_(size_t(ParameterId::count_)) {
clear(std::integral_constant<int, int(ParameterId::count_) - 1>());
}
// getter for when you know the id at compile time. Should have better
// error checking.
template<ParameterId pid>
typename ConfigTraits<pid>::type get() {
// The following will segfault if the value has the wrong type.
return *boost::get<typename ConfigTraits<pid>::type>(&values_[int(pid)]);
}
// setter when you know the id at compile time
template<ParameterId pid>
void set(typename ConfigTraits<pid>::type new_val) {
values_[int(pid)] = new_val;
}
// getter for an id known only at runtime; returns a boost::variant;
value_type get(ParameterId pid) {
return values_[int(pid)];
}
private:
// Initialize parameters to default values of the correct type
template<int I> void clear(std::integral_constant<int, I>) {
values_[I] = param_type<ParameterId(I)>();
clear(std::integral_constant<int, I - 1>());
}
void clear(std::integral_constant<int, 0>) {
values_[0] = param_type<ParameterId(0)>();
}
std::vector<value_type> values_;
};
// And finally, a little test
#include <iostream>
int main() {
Parameters parms;
std::cout << ('(' + parms.get<ParameterId::name>() + ')')<< ' '
<< parms.get<ParameterId::is_elephant>() << ' '
<< parms.get<ParameterId::caloric_intake>() << ' '
<< parms.get<ParameterId::legs>() << std::endl;
parms.set<ParameterId::is_elephant>(true);
parms.set<ParameterId::caloric_intake>(27183.25);
parms.set<ParameterId::legs>(4);
parms.set<ParameterId::name>("jumbo");
std::cout << ('(' + parms.get<ParameterId::name>() + ')')<< ' '
<< parms.get<ParameterId::is_elephant>() << ' '
<< parms.get<ParameterId::caloric_intake>() << ' '
<< parms.get<ParameterId::legs>() << std::endl;
return 0;
}
Для тех, кто еще не может использовать C ++ 11, вот версия, которая использует не-классные перечисления и которая недостаточно умна для создания boost::variant
введите сам, поэтому вы должны предоставить его вручную:
#include <string>
#include <vector>
#include <boost/variant.hpp>
#include <boost/variant/get.hpp>
// Here's how you define your enums, and what they represent:
struct ParameterId {
enum Id {
is_elephant = 0,
caloric_intake,
legs,
name,
// ...
count_
};
};
template<int> struct ConfigTraits;
// Definition of type of each enum
template<> struct ConfigTraits<ParameterId::is_elephant> {
typedef bool type;
};
template<> struct ConfigTraits<ParameterId::caloric_intake> {
typedef double type;
};
template<> struct ConfigTraits<ParameterId::legs> {
typedef int type;
};
template<> struct ConfigTraits<ParameterId::name> {
typedef std::string type;
};
// ...
// Here's the stuff that makes it work.
// C++03 doesn't have integral_constant, so we need to roll our own:
template<int I> struct IntegralConstant { static const int value = I; };
template<typename VARIANT>
class Parameters {
public:
typedef VARIANT value_type;
// It would be better to not initialize all the values twice, but this
// was easier.
Parameters() : values_(size_t(ParameterId::count_)) {
clear(IntegralConstant<int(ParameterId::count_) - 1>());
}
// getter for when you know the id at compile time. Should have better
// error checking.
template<ParameterId::Id pid>
typename ConfigTraits<pid>::type get() {
// The following will segfault if the value has the wrong type.
return *boost::get<typename ConfigTraits<pid>::type>(&values_[int(pid)]);
}
// setter when you know the id at compile time
template<ParameterId::Id pid>
void set(typename ConfigTraits<pid>::type new_val) {
values_[int(pid)] = new_val;
}
// getter for an id known only at runtime; returns a boost::variant;
value_type get(ParameterId::Id pid) {
return values_[int(pid)];
}
private:
// Initialize parameters to default values of the correct type
template<int I> void clear(IntegralConstant<I>) {
values_[I] = typename ConfigTraits<I>::type();
clear(IntegralConstant<I - 1>());
}
void clear(IntegralConstant<0>) {
values_[0] = typename ConfigTraits<0>::type();
}
std::vector<value_type> values_;
};
// And finally, a little test
#include <iostream>
int main() {
Parameters<boost::variant<bool, int, double, std::string> > parms;
std::cout << ('(' + parms.get<ParameterId::name>() + ')')<< ' '
<< parms.get<ParameterId::is_elephant>() << ' '
<< parms.get<ParameterId::caloric_intake>() << ' '
<< parms.get<ParameterId::legs>() << std::endl;
parms.set<ParameterId::is_elephant>(true);
parms.set<ParameterId::caloric_intake>(27183.25);
parms.set<ParameterId::legs>(4);
parms.set<ParameterId::name>("jumbo");
std::cout << ('(' + parms.get<ParameterId::name>() + ')')<< ' '
<< parms.get<ParameterId::is_elephant>() << ' '
<< parms.get<ParameterId::caloric_intake>() << ' '
<< parms.get<ParameterId::legs>() << std::endl;
return 0;
}
Других решений пока нет …