【问题标题】:Fill runtime data at compile time with templates在编译时使用模板填充运行时数据
【发布时间】:2017-03-22 06:30:54
【问题描述】:

我有一个特定的情况,我想在编译时准备一些运行时结构,而不需要复制代码。

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

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!"); };

这些用于配置标头中的宏,该宏专门针对我需要的多种类型 Type&lt;T&gt;

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&lt;TypeID, std::string&gt;。当然,这也意味着当大部分信息在编译时通过类型定义已经可用时,我必须维护两倍的代码。

我想知道是否有一些我缺少的晦涩技巧可以合并这些,以便REGISTER_TYPE 宏也注册运行时信息。我还没有带来任何东西,但也许有一个聪明的方法来管理它。

【问题讨论】:

    标签: c++ c++11 templates c-preprocessor c++14


    【解决方案1】:

    我已经看到你使用 c++ 扩展 __COUNTER__,因此也许你会发现有趣的字符串文字 gcc 和 clang 扩展,这将允许你将表示注册类型的字符串文字直接绑定到你的 Type(无需额外的标识号):

    #include <iostream>
    
    template <class Char, Char... Cs>
    struct string_literal { 
        static Char str[sizeof...(Cs) + 1]; 
    };
    
    template <class Char, Char... Cs>
    Char string_literal<Char, Cs...>::str[sizeof...(Cs) + 1] = { Cs..., '\0' };
    
    template <class Char, Char... Cs>
    constexpr string_literal<Char, Cs...> operator ""_sl() {
        return {};
    }
    
    template <class T, class SL>
    struct TypeHelper { };
    
    template <class T>
    struct Type;
    
    template <class A, class B>
    auto getType(TypeHelper<A, B>) {
        return B{};
    }
    
    #define REGISTER(TYPE) template <> \
         struct Type<TYPE>: TypeHelper<TYPE, decltype(#TYPE##_sl)> { };
    
    struct X{};
    
    REGISTER(void)
    REGISTER(int)
    REGISTER(X)
    
    int main(){
        std::cout << decltype(getType(Type<void>{}))::str << std::endl;
    }
    

    输出:

    无效

    [live demo]

    【讨论】:

      【解决方案2】:

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


      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 扩展冲突的唯一名称。

      【讨论】:

      • 行号不会替换unused_registrar_ ## __LINE__ ## type 中的__LINE__。您必须使用辅助宏。
      猜你喜欢
      • 2015-10-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-10-28
      • 1970-01-01
      • 2015-08-23
      • 2016-09-17
      相关资源
      最近更新 更多