12

我有一个可以容纳不同类型的属性向量:

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

所有可能的类型在编译时都是已知的。任何建议都受到高度赞赏。在一个完美的解决方案中,我会在一个地方创建名称-> 类型映射。之后,我会这样使用它

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

这种魔法不需要在编译时发生。我的目标是拥有尽可能易读的代码。

4

5 回答 5

10

我会使用一个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>

当然,如果你只知道运行时的字符串值,这个解决方案是有效的。

于 2016-07-28T18:55:58.640 回答
8
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);

演示

于 2016-07-28T19:13:02.403 回答
1

我可能会做这样的事情:

特征:

  • 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");


}
于 2016-07-28T19:19:09.353 回答
1

很大程度上基于 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");
}
于 2016-07-28T19:23:52.857 回答
-1

简短回答:不,您不能指示编译器在编译时评估运行时条件。连花都没有。

长答案:有一些(主要是与语言无关的)模式。

我假设你base_attribute_vector有一些virtual方法,很可能,在其他语言中pure通常称为 an 。interface

这意味着根据您实际问题的复杂性,您可能需要一个工厂抽象工厂

您可以在 C++ 中创建没有虚拟方法的工厂或抽象工厂,并且可以使用 hana。但问题是:增加的复杂性真的值得(可能真的很小)性能提升吗?

(此外,如果您想消除每个虚拟呼叫,即使是来自,您必须在切换发生的入口点之后使用该类base_attribute_vector使所有内容成为模板)

我的意思是,您是否使用虚拟方法实现了这一点,并测量到虚拟调用的成本太高了?

编辑:另一种但不同的解决方案可能是对访问者使用变体类型,例如egg::variant

使用variant,您可以为每种参数类型创建具有函数的类,并且该apply方法将根据其运行时类型切换要运行的函数。

就像是:

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

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

如果它们有共同的部分,你甚至可以使用模板化的操作符(可能使用 enable_if/sfinae)。

于 2016-07-28T18:46:02.760 回答