Выберите шаблон на основе строки времени выполнения в переполнении стека

У меня есть атрибут вектор, который может содержать различные типы:

class base_attribute_vector; // no template args

template<typename T>
class raw_attribute_vector : public base_attribute_vector;

raw_attribute_vector<int> foo;
raw_attribute_vector<std::string> foo;

Основываясь на вводе во время выполнения для типа, я хотел бы создать соответствующую структуру данных. псевдокод:

std::string type("int");
raw_attribute_vector<type> foo;

Очевидно, что это не удается. Простой, но уродливый и не поддерживаемый обходной путь — это переключатель времени выполнения / цепочка, если:

base_attribute_vector *foo;
if(type == "int") foo = new raw_attribute_vector<int>;
else if(type == "string") ...

Я читал о полиморфизме во время выполнения с функторами, но нашел его довольно сложным для задачи, которая концептуально проста.

Какой самый лучший и чистый способ сделать эту работу? Я играл с boost::hanaОбнаружив, что, хотя я могу создать отображение из строки в тип, поиск может быть выполнен только во время компиляции:

auto types =
hana::make_map(
hana::make_pair(BOOST_HANA_STRING("int"), hana::type_c<int>),
hana::make_pair(BOOST_HANA_STRING("string"), hana::type_c<std::string>)
);

Все возможные типы известны во время компиляции. Любые предложения высоко ценятся. В идеальном решении, я бы создал отображение name-> type в одном месте. После этого я бы использовал это так

std::vector<base_attribute_vector*> foo;

foo.push_back(magic::make_templated<raw_attribute_vector, "int">);
foo.push_back(magic::make_templated<raw_attribute_vector, "std::string">);

foo[0]->insert(123);
foo[1]->insert("bla");

foo[0]->print();
foo[1]->print();

Не требуется, чтобы это волшебство происходило во время компиляции. Моя цель — сделать код максимально читабельным.

6

Решение

enum class Type
{
Int,
String,
// ...
Unknown
};

Type TypeFromString(const std::string& s)
{
if (s == "int") { return Type::Int; }
if (s == "string") { return Type::String; }
// ...
return Type::Unknown;
}

template <template <typename> class>
struct base_of;

template <template <typename> class C>
using base_of_t = typename base_of<C>::type;

А потом родовой завод

template <template <typename> class C>
std::unique_ptr<base_of_t<C>> make_templated(const std::string& typeStr)
{
Type type = TypeFromString(typeStr);
static const std::map<Type, std::function<std::unique_ptr<base_of_t<C>>()>> factory{
{Type::Int, [] { return std::make_unique<C<int>>(); } },
{Type::String, [] { return std::make_unique<C<std::string>>(); } },
// ...
{Type::Unknown, [] { return nullptr; } }
};
return factory.at(type)();
}

специализация необходима для каждой базы:

template <>
struct base_of<raw_attribute_vector> {
using type = base_attribute_vector;
};

А потом

auto p = make_templated<raw_attribute_vector>(s);

демонстрация

4

Другие решения

Я бы использовал std::map который имеет строки в качестве ключа и std::function как значения. Я бы связал строку с функцией, которая возвращает ваш тип. Вот пример:

using functionType = std::function<std::unique_ptr<base_attribute_vector>()>;
std::map<std::string, functionType> theMap;

theMap.emplace("int", []{ return new raw_attribute_vector<int>; });
theMap.emplace("float", []{ return new raw_attribute_vector<float>; });

// Using the map
auto base_vec = theMap["int"](); // base_vec is an instance of raw_attribute_vector<int>

Конечно, это решение действительно, если вы знаете только строковое значение во время выполнения.

4

Я бы, наверное, сделал что-то вроде этого:

Особенности:

  • 1 — время регистрации объектов путем передачи названного прототипа

  • постоянное время поиска во время выполнения

  • поиск по любому типу, который можно сравнить с std::string

#include <unordered_map>
#include <string>struct base_attribute_vector { virtual ~base_attribute_vector() = default; };

template<class Type> struct attribute_vector : base_attribute_vector {};

// copyable singleton makes handling a breeze
struct vector_factory
{
using ptr_type = std::unique_ptr<base_attribute_vector>;

template<class T>
vector_factory add(std::string name, T)
{
get_impl()._generators.emplace(std::move(name),
[]() -> ptr_type
{
return std::make_unique< attribute_vector<T> >();
});
return *this;

}

template<class StringLike>
ptr_type create(StringLike&& s) const {
return get_impl()._generators.at(s)();
}

private:
using generator_type = std::function<ptr_type()>;

struct impl
{
std::unordered_map<std::string, generator_type, std::hash<std::string>, std::equal_to<>> _generators;
};private:

static impl& get_impl() {
static impl _ {};
return _;
}

};// one-time registration

static const auto factory =
vector_factory()
.add("int", int())
.add("double", double())
.add("string", std::string());int main()
{
auto v = factory.create("int");
auto is = vector_factory().create("int");

auto strs = vector_factory().create("string");}
1

Ты не сможешь это сделать. В лучшем случае вам нужно поддерживать ограниченное количество типов и переключаться между ними, используя if утверждение, которое может быть оценено во время компиляции.

0

Краткий ответ: нет, вы не можете дать указание компилятору оценивать условия выполнения во время компиляции. Даже с ханой.

Длинный ответ: для этого есть несколько (в основном не зависящих от языка) шаблонов.

Я предполагаю, что ваш base_attribute_vector имеет некоторые virtual метод, скорее всего pureобычно называется interface на других языках.

Это означает, что в зависимости от сложности вашей реальной проблемы, вы, вероятно, хотите завод или абстрактная фабрика.

Вы можете создать фабрику или абстрактную фабрику без виртуальных методов в C ++, и вы можете использовать для этого хану. Но вопрос в следующем: действительно ли дополнительная сложность того стоит (возможно, очень незначительного) прироста производительности?

(также, если вы хотите исключить каждый виртуальный вызов, даже base_attribute_vectorнужно сделать все используя этот класс шаблон, после точки входа, где происходит переключение)

Я имею в виду, реализовали ли вы это с помощью виртуальных методов и измерили, что стоимость виртуальных вызовов слишком велика?

Изменить: другое, но другое решение может быть использование варианта типа с посетителями, как яйца :: вариант.

С variant, вы можете создавать классы с функциями для каждого типа параметра, и apply Метод будет переключать какую функцию запускать в зависимости от ее типа во время выполнения.

Что-то вроде:

struct handler {
void operator()(TypeA const&)  { ... }
void operator()(TypeB const&)  { ... }
// ...
};

eggs::variant< ... > v;
eggs::variants::apply(handler{}, v);

Вы даже можете использовать шаблонные операторы (возможно, с enable_if / sfinae), если они имеют общие части.

0

Основываясь на ответе Jarod42, я буду использовать это:

class base_attribute_vector {};

template<typename T>
class raw_attribute_vector : public base_attribute_vector {
public:
raw_attribute_vector() {std::cout << typeid(T).name() << std::endl; }
};

template<class base, template <typename> class impl>
base* magic(std::string type) {
if(type == "int") return new impl<int>();
else if(type == "float") return new impl<float>();
}

int main() {
auto x = magic<base_attribute_vector, raw_attribute_vector>("int");
auto y = magic<base_attribute_vector, raw_attribute_vector>("float");
}
0
По вопросам рекламы ammmcru@yandex.ru
Adblock
detector