Choose template based on run-time string in C++

后端 未结 6 710
终归单人心
终归单人心 2020-12-14 11:39

I have an attribute vector that can hold different types:

class base_attribute_vector; // no template args

template
class raw_attribute_ve         


        
相关标签:
6条回答
  • 2020-12-14 12:15
    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;
    

    And then the generic factory

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

    a specialization is needed for each base:

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

    And then

    auto p = make_templated<raw_attribute_vector>(s);
    

    Demo

    0 讨论(0)
  • 2020-12-14 12:17

    Short answer: no, you can't instruct the compiler to evaluate a runtime condition in compile time. Not even with hana.

    Long answer: there are some (mostly language independent) patterns for this.

    I'm assuming that your base_attribute_vector has some virtual method, most likely pure, commonly called an interface in other languages.

    Which means that depending on the complexity of your real problem, you probably want a factory or an abstract factory.

    You could create a factory or abstract factory without virtual methods in C++, and you could use hana for that. But the question is: is the added complexity really worth it for that (possibly really minor) performance gain?

    (also if you want to eliminate every virtual call, even from base_attribute_vector, you have to make everything using that class a template, after the entry point where the switch happens)

    I mean, have you implemented this with virtual methods, and measured that the cost of the virtual calls is too significant?

    Edit: another, but different solution could be using a variant type with visitors, like eggs::variant.

    With variant, you can create classes with functions for each parameter type, and the apply method will switch which function to run based on it's runtime type.

    Something like:

    struct handler {
      void operator()(TypeA const&)  { ... }
      void operator()(TypeB const&)  { ... }
      // ...
    };
    
    eggs::variant< ... > v;
    eggs::variants::apply(handler{}, v);
    

    You can even use templated operators (possibly with enable_if/sfinae), if they have common parts.

    0 讨论(0)
  • 2020-12-14 12:20

    Largely based on Jarod42's answer, this is what I will be using:

    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 讨论(0)
  • 2020-12-14 12:31

    You cannot do this. At best, you need to support a limited number of types, and switch between them using an if statement that can be evaluated at compile time.

    0 讨论(0)
  • 2020-12-14 12:33

    I'd use an std::map that has strings as key and std::function as values. I would associate the string with a function that returns your type. Here's an example:

    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>
    

    Of course, this solution is valid if you only know the string value at runtime.

    0 讨论(0)
  • 2020-12-14 12:41

    I'd probably do something like this:

    Features:

    • 1 - time registration of objects by passing a named prototype

    • constant time lookup at runtime

    • lookup by any type which can be compared to 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");
    
    
    }
    
    0 讨论(0)
提交回复
热议问题