我有一种特定的情况,我想在编译时准备一些运行时结构,而无需重复代码。

我有两个结构,可用于在编译时为我编写的编译器注册某些类型:

using TypeID = u8;

template<typename T, typename TYPE_ID, TYPE_ID I>
struct TypeHelper
{
  static constexpr TYPE_ID value = std::integral_constant<TYPE_ID, I>::value;
};

template<typename T> struct Type : TypeHelper<T, u8, __COUNTER__> { static_assert(!std::is_same<T,T>::value, "Must specialize for type!"); };

这些在配置 header 中使用,该 header 带有专门针对我需要的多种类型的Type<T>的宏:
using type_size = unsigned char;

#define GET_NTH_MACRO(_1,_2,_3, NAME,...) NAME
#define REGISTER_TYPE(...) GET_NTH_MACRO(__VA_ARGS__, REGISTER_TYPE3, REGISTER_TYPE2, REGISTER_TYPE1)(__VA_ARGS__)

#define REGISTER_TYPE1(_TYPE_) REGISTER_TYPE2(_TYPE_, _TYPE_)

#define REGISTER_TYPE2(_TYPE_,_NAME_) \
constexpr TypeID TYPE_##_NAME_ = __COUNTER__; \
template<> struct Type<_TYPE_> : TypeHelper<_TYPE_, type_size, TYPE_##_NAME_> { \
  static constexpr const char* name = #_NAME_; \
};

REGISTER_TYPE(void)
REGISTER_TYPE(s64)
REGISTER_TYPE(s32)

这样它们扩展到
constexpr TypeID TYPE_void = 2;
template<> struct Type<void> : TypeHelper<void, type_size, TYPE_void> { static constexpr const char* name = "void"; };

constexpr TypeID TYPE_s64 = 3;
template<> struct Type<s64> : TypeHelper<s64, type_size, TYPE_s64> { static constexpr const char* name = "s64"; };

constexpr TypeID TYPE_s32 = 4;
template<> struct Type<s32> : TypeHelper<s32, type_size, TYPE_s32> { static constexpr const char* name = "s32"; };

这工作正常,但编译器还需要有关这些类型的一些运行时信息,因此,除此以外,我还必须定义一些辅助功能,例如
static TypeID typeForIdent(const std::string& name);
static const char* nameForType(TypeID type);
static void mapTypeName(TypeID type, const std::string& name);

inline bool isSigned(TypeID type)
{
  return type == Type<s8>::value || type == Type<s16>::value ||
  type == Type<s32>::value || type == Type<s64>::value;
}

和类似的功能。

这些函数必须在没有模板参数的情况下工作,因此TypeID必须是常规参数。但是我需要在代码的单独部分中初始化此类数据,例如:
mapTypeName(Type<s32>::value, "s32");

它使用静态std::unordered_map<TypeID, std::string>。当然,这还意味着当大多数信息在编译时通过类型定义已经可用时,我必须对代码进行两次维护。

我想知道是否缺少一些晦涩的技巧,这些技巧可以将它们融合在一起,以便REGISTER_TYPE宏也可以注册运行时信息。我还没有附带任何东西,但是也许有一种巧妙的方法可以解决这个问题。

最佳答案

如果您不特别关心将运行时数据注册到映射中的性能,则可以简单地使用inline函数,该函数返回对static映射实例的引用,并在registrar宏中生成“dummy” REGISTER_TYPE实例,以填充他们的构造函数中的 map 。

inline auto& registration_map()
{
    static std::unordered_map<int, std::string> m;
    return m;
}

struct registrar
{
    registrar(int id, std::string s)
    {
        registration_map()[id] = std::move(s);
    }
};

template <typename T>
struct TypeHelper { };

#define CAT3_IMPL(a, b, c) a ## b ## c
#define CAT3(a, b, c) CAT3_IMPL(a, b, c)

#define REGISTER_TYPE(id, type) \
    template<> struct TypeHelper<type> { }; \
    [[maybe_unused]] registrar CAT3(unused_registrar_, __LINE__, type) {id, #type};

REGISTER_TYPE(0, int)
REGISTER_TYPE(1, float)
REGISTER_TYPE(2, double)

int main()
{
    assert(registration_map()[0] == "int");
    assert(registration_map()[1] == "float");
    assert(registration_map()[2] == "double");
}

Full example on wandbox

笔记:
  • 如果多个翻译单元中包含相同的REGISTER_TYPE,则可能会重复注册。
  • CAT3(unused_registrar_, __LINE__, type)用于生成唯一名称,该名称不会与其他REGISTER_TYPE扩展冲突。
  • 关于c++ - 在编译时使用模板填充运行时数据,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/40490166/

    10-11 00:43
    查看更多