【问题标题】:C++ Mapping a set of values to typesC++ 将一组值映射到类型
【发布时间】:2019-03-15 13:05:11
【问题描述】:

我的最终目标是实现从一组两个字符串到对应数据类型的映射:

"AA" --> char
"BB" --> int
"CC" --> float
"DD" --> std::complex<double>
and so on ...

我能想到的最好的“不太有效”的解决方案是两部分。第一部分是使用 std::map 在字符串和相应的枚举值之间进行映射。第二部分使用模板化类型别名和 std::conditional 将枚举值映射到类型。

enum class StrEnum { AA, BB, CC, DD };
// STEP 1: string --> enum
// !! std::map will not work here since a constexpr is required !!
std::map<std::string, StrEnum> str_map = {
    {"AA", StrEnum::AA},
    {"BB", StrEnum::BB},
    {"CC", StrEnum::CC},
    {"DD", StrEnum::DD}
};
// STEP 2: enum --> type
template<StrEnum val> using StrType = typename std::conditional<
   val == StrEnum::AA,
   char,
   typename std::conditional<
       val == StrEnum::BB,
       int,
       typename std::conditional<
           val == StrEnum::CC,
           float,
           std::complex<double>
       >::type
   >::type
>::type;

目标用法: StrType&lt;str_map["BB"]&gt; myVal; // &lt;-- does not work in current state with std::map

随着更多值映射的添加,上述嵌套会变得非常糟糕。

是否有更好/更清洁/有效的整体方法来实现此映射?我对第 2 步以及是否有办法减少嵌套特别感兴趣。

我正在使用 C++11。 (但如果唯一的答案在于 C++14 或更高版本,那么至少知道它会很好)

【问题讨论】:

  • 明确一点:您希望此映射在编译时工作?
  • 这段代码真的按原样工作吗?
  • Hana 可能对您有用,但将两个字符的键转换为类型可能有点尴尬。
  • 请注意,您的代码缺少几个 typename(在每个 std::conditional 之前)以符合要求。我猜你正在使用 MSVC?
  • 我对缺少的类型名不好。我编辑了帖子以添加它们。我实际上使用 gcc 作为我的编译器。 @ Max Langhof,这个映射应该在理想的编译时工作。

标签: c++ c++11 templates


【解决方案1】:

由于std::map 没有常量表达式,您的模板参数str_map["BB"] 无法在编译时计算。

整数映射到类型 的一种简单且可维护的方法是使用std::tuplestd::tuple_element,如下所示。 例如,StrType&lt;0&gt;charStrType&lt;1&gt;int,等等:

using types = std::tuple<char, int, float, std::complex<double>>;

template<std::size_t N>
using StrType = typename std::tuple_element<N, types>::type;

那么问题是如何在C++11中将字符串映射为整数。 首先,本文中的accepted answer 可以在编译时比较字符串。 其次,我们可以在编译时评估中使用三元运算符。 因此,至少以下函数getIdx 可以在编译时将每个字符串映射到相应的整数。 例如,getIdx("AA") 为零:

constexpr bool strings_equal(const char* a, const char* b) {
    return *a == *b && (*a == '\0' || strings_equal(a + 1, b + 1));
}

constexpr std::size_t getIdx(const char* name) 
{
    return strings_equal(name, "AA") ? 0:
           strings_equal(name, "BB") ? 1:
           strings_equal(name, "CC") ? 2:
           strings_equal(name, "DD") ? 3:
                                       4; // compilation error
}

您可以将这些函数用于当前目的,如下所示:

DEMO

StrType<getIdx("BB")> x; // x is int.

constexpr const char* float_type = "CC";
StrType<getIdx(float_type)> y; // y is float.

static_assert(std::is_same<StrType<getIdx("AA")>, char> ::value, "oops."); // OK.
static_assert(std::is_same<StrType<getIdx("BB")>, int>  ::value, "oops."); // OK.
static_assert(std::is_same<StrType<getIdx("CC")>, float>::value, "oops."); // OK.
static_assert(std::is_same<StrType<getIdx("DD")>, std::complex<double>>::value, "oops."); // OK.

【讨论】:

  • 我认为这基本上解决了它。 constexpr 字符串比较函数是我从未想过的真正缺失的环节。
【解决方案2】:

我最近在做类似的事情。我提出的解决方案是这样的(有更多的东西,但这里是主要思想):

//Define a Type for ID
using TypeIdentifier = size_t;

//Define a ID generator
struct id_generator {
    static TypeIdentifier create_id() {
        static TypeIdentifier value = 0;
        return value++;
    }
};

//Define some kind of handler for place 
struct type_id_handler {
    static std::vector<std::function<void(void*)>> type_handler;
};

std::vector<std::function<void(void*)>> type_id_handler::type_handler;

//Define id's and make a basic functions
template<typename T>
struct type_id_define {
    static TypeIdentifier get_id() {
        static TypeIdentifier id = id_generator::create_id();
        static auto one_time_stuff = [] () -> bool {
            type_id_handler::type_handler.resize(id+1);
            type_id_handler::type_handler[id] = [](void* ptr) {
                auto * object = static_cast<T*>(ptr);
                //do stuff your type
                std::cout << __PRETTY_FUNCTION__ << std::endl;
            };
            return true;
        }();

        return id;
    }
};

对于主要的虚拟测试:

int main() {

    std::map<std::string, TypeIdentifier> typeMap {
            {"AA", type_id_define<char>::get_id()},
            {"BB", type_id_define<int>::get_id()},
    };

    int a;
    char b;
    type_id_handler::type_handler[typeMap["BB"]](&a);
    type_id_handler::type_handler[typeMap["AA"]](&b);
    return 0;
}

输出应该是:

type_id_define<T>::get_id()::<lambda()>::<lambda(void*)> [with T = int]
type_id_define<T>::get_id()::<lambda()>::<lambda(void*)> [with T = char]

主要思想是为每种类型创建一个具有正确 id 的新type_id_define,并将其用作选择要执行的正确函数的索引。此外,在生成 id 时,它会存储一个从 void* 转换为给定类型的函数(我使用 void* 在同一向量上存储不同类型的函数)之后您可以使用 std::any、void* 或任何您想要的将对象传递给函数并获得类型安全。

如果您想使用类似的东西我也建议您考虑更好的寄存器类型并添加相应的功能。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2023-04-04
    • 2019-04-09
    • 2021-04-11
    • 2019-08-17
    • 1970-01-01
    • 1970-01-01
    • 2011-05-30
    • 2012-06-16
    相关资源
    最近更新 更多