如何在C ++中根据用户输入定义数据类型?

时间:2019-11-02 05:24:22

标签: c++ templates

我有HashMap类:

template<typename K, typename T>
class HashNode
{
 ...
};

template<typename K, typename T>
class HashMap
{
...
};

我想从HashMap的用户数据类型中获取。例如:

用户在命令行中出现问题:“ I S”

这意味着int表示键,字符串表示值

结果:

HashMap<int, string>*my_map = new HashMap<int, string>(capacity);

我尝试使用switch命令和reinterpret_cast <>解决此问题,但它看起来太冗长。

switch ((int)typename1)
{
    case('I'):
        switch((int)typename2)
        {
            case('I'):
                HashMap<int, int>*table1 = new HashMap<int, int>(20);
                return table1;
            case('D'):
                HashMap<int, double>*table2 = new HashMap<int, double>(20);
                return table2;
         ...
}

还有什么替代方法?

谢谢

1 个答案:

答案 0 :(得分:1)

这是过度设计,但是我今天要面对挑战...

因此,您要返回基于用户输入选择的N个哈希图类型之一吗? std::variant是适合该工作的类型。 variant<A,B,C>是一种求和类型,可以包含类型ABC中的一种。它已添加到C ++ 17,但是您可以找到像this one这样在C ++ 11中工作的单头实现。

您发现,模板是生成样板的最佳方法。您似乎想根据字符选择哈希映射值类型,所以让我们为自己提供一种在模板中定义映射的方法:

template <char C, typename TValue>
struct Opt { };

然后我们的目标是通过列出一堆这些选项并传递用户输入来生成您的开关:

auto table = CreateHashMap<Opt<'i', int>, Opt<'d', double>, /*etc*/>(user_input);

表在哪里

std::variant<std::monostate, HashMap<int, int>, HashMap<int, double>, /*etc*/>

(第一个选项std::monostate是该函数失败的“空”选项)

生成的代码是:

std::variant<std::monostate, HashMap<int, int>, HashMap<int, double>/*, etc */> CreateHashMap(char user_input) {
    switch (user_input) {
        case 'i':
            return HashMap<int, int>(60);
        case 'd':
            return HashMap<int, double>(60);
        /* case 'e': return etc; */
        default:
            return std::monostate{};
    }
}

步骤1-从选项列表中推导变体类型

这可以通过从std::variant<std::monostate>开始,然后递归地逐一附加类型来完成:

template <typename... TOpts>
struct OptToVariantImpl;

// Recursive case - pass TValue to the variant
template <typename... TValues, char Code, typename TValue, typename... TOpts>
struct OptToVariantImpl<std::variant<TValues...>, Opt<Code, TValue>, TOpts...> 
    : OptToVariantImpl<std::variant<TValues..., HashMap<int, TValue>>, TOpts...> { };

// Terminal case - just expose the variant
template <typename TVariant>
struct OptToVariantImpl<TVariant> {
    using Type = TVariant;
};

// Initial variant just has an empty state.
template <typename... TOpts>
using OptToVariant = typename OptToVariantImpl<std::variant<std::monostate>, TOpts...>::Type;

第2步-生成开关

我们对每个选项的“切换”很容易,因为所有选项都会产生相同的结果,即变体。您可以将它们链接成一个大的递归三元组:

return user_input == 'Code' ? return /*HashMapForThisOpt*/ : /*Pass the buck*/; 

一个聪明的O3编译器应该能够内联该代码,并保留一个switch块。

填写的代码为:

template <typename... TValues>
struct CreateHashMapImpl;

// Recursive case - Test code and return hashcode or evaluate next option
template <typename TVariant, char Code, typename TValue, typename... TOpts>
struct CreateHashMapImpl<TVariant, Opt<Code, TValue>, TOpts...> 
    : CreateHashMapImpl<TVariant, TOpts...> {
    constexpr TVariant operator()(char code) {
        return code == Code ? TVariant { HashMap<int, TValue>(60) } : CreateHashMapImpl<TVariant, TOpts...>::operator()(code);
    };
};

// Terminal case - return std::monostate for failure result.
template <typename TVariant>
struct CreateHashMapImpl<TVariant> {
    constexpr TVariant operator()(char code) { return TVariant { std::monostate{} }; }
} ;

template <typename... TOpts>
OptToVariant<TOpts...> CreateHashMap(char code) {
    return CreateHashMapImpl<OptToVariant<TOpts...>, TOpts...>{}(code);
}

第3步-使用

auto table = CreateHashMap<Opt<'i', int>, Opt<'d', double>, Opt<'c', char>>(user_input);
if (auto* result = std::get_if<HashMap<int, double>>(&table)) {
    std::cout << "double: " << result->find(42)->second;
}

将所有内容放在一起: https://godbolt.org/z/VBnMq2

您可以看到该示例将优化过程简化为一个简单的开关。