【问题标题】:Metafunction to convert a type to an integer and vice-versa将类型转换为整数的元函数,反之亦然
【发布时间】:2015-12-11 18:26:58
【问题描述】:

typeid 允许在运行时为每种类型分配唯一的std::type_index。我也想做同样的事情,静态地使用两个元函数:

// Get a unique integral number associated with the provided type
template <class T>
struct encode_type
{
    using type = T;
    static constexpr std::size_t value = /* Metaprogramming magic */;
};

// Get the type uniquely associated with the provided value
template <std::size_t V>
struct decode_type
{
    static constexpr std::size_t value = V;
    using type = /* Metaprogramming magic */;
};

有没有办法在 C++11 中做到这一点?

【问题讨论】:

  • 为什么要这个,因为在编译时你得到了类型本身?
  • 排除一些邪恶的黑客,我认为在编译时计数(或生成其他唯一整数)是不可能的,除非你愿意枚举所有类型这应该可以工作(tuple&lt;Ts...&gt;,然后使用索引)。
  • 正如我在下面指出的,答案是“不”。但是,您的要求可能比实际需要的多。很可能您有一个应用程序,而这正是您想要尝试的。但是你没有在上面包括你的首要动机。随意提出一个问题,包括您在另一个问题中真正想做什么!
  • 另一个潜在的困难与编译单元之间的链接/协议有关。 Yakk 已经表明你不能为所有类型普遍实现它。有人建议你可以在一个元组中枚举一个它应该工作的类型的全局列表,但这没有吸引力。您可以想象使用宏自动累积应该工作的类型列表,或者更好的是,懒惰地“注册”类型,即在评估元函数时。但我认为在编译单元之间保持类型编号对同步是非常困难的。
  • 类似于thisthis 的东西可能会起作用,但CWG hates it,所以我看不出有什么意义。

标签: c++ templates c++11 types template-meta-programming


【解决方案1】:

这是一个与 GCC 5.2 和 Clang 3.7 一起“工作”的可能解决方案。

我使用Filip RoséenConstexpr Meta-Container 稍作改动。作为 T.C.指出,未来可能是made ill-formed,所以这个解决方案在生产代码中是完全不合理的,但现在已经很酷了。我什至不确定这是否 100% 符合标准。

// This is our meta-container
using TypeMap = atch::meta_list<class A>;

// Get a unique integral number associated with the provided type
template <class T>
struct encode_type
{
    using type = T;
    // Push T into the container and store the pre-push size
    //( requires slight change to Filip's code)
    static constexpr std::size_t value = TypeMap::push<T>();
};

// Get the type uniquely associated with the provided value
template <std::size_t V>
struct decode_type
{
    static constexpr std::size_t value = V;
    // Get the type at index V
    // (requires a small helper function addition)
    using type = decltype(TypeMap::at<V>());
};

我对原代码所做的改动:

template<class T, class H = meta_list, std::size_t Size = counter::value()>
static constexpr std::size_t push (
  size_type = push_state<
    typename H::template value<>::template push<T>::result
  > ()
) { return Size; } 

我修改了atch::meta_list::push 以返回元容器推送之前的大小。我使用了带有默认参数的模板参数,以确保在推送之前计算大小。

template<size_type Idx, class H = meta_list>
static constexpr auto at () -> typename H::template value<>::template at<Idx>::result;

我在atch::meta_list 中添加了一个小的decltype 辅助函数,以隐藏所有依赖名称的混乱。


一些测试代码:

int main () {
    std::array<int, 4> encoded { 
        encode_type<int>::value,
        encode_type<double>::value,
        encode_type<std::string>::value,
        encode_type<float>::value
    };
  
  std::cout << "Encoding: ";
  for (auto i : encoded) std::cout << i << ", ";
  std::cout << std::endl;
  
  std::array<std::type_index, 4> decoded {
      typeid(decode_type<0>::type),  
      typeid(decode_type<1>::type),
      typeid(decode_type<2>::type),
      typeid(decode_type<3>::type),
  };
  
  std::cout << "Decoding: ";
  for (auto i : decoded) std::cout << i.name() << ", ";
  std::cout << std::endl;
}

Clang 和 GCC 都发出一堆警告,但它们都“有效”!

叮当compiles, runs and outputs:

编码:0、1、2、3,

解码:i,d,NSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE,f,

GCCcompiles, runs and outputs:

编码:0、1、2、3,

解码:i,d,NSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE,f,


也许你会更好的预处理步骤......

【讨论】:

  • 我不确定这是否有用,但显然这很有趣!谢谢!
  • 这是否处理多个encode_type&lt;int&gt; 调用? (并且不会重新编码一个新值)
  • @Yakk 是的,编码是通过模板实例化完成的,因此使用相同参数的多次调用是无操作的。
  • 如何访问编码类型为 NSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE 的东西的值?
【解决方案2】:

C++ 中的类型多于size_t 所能计算的类型。

作为证据:

template<size_t N> struct bob {};

bob&lt;X&gt; 类型的数量与 size_t 值的数量完全相同。

template<class...> struct alice {};

现在,alice&lt;bob&lt;1&gt;, bob&lt;2&gt;&gt; 是一个有效类型。 alice&lt;alice&lt;bob&lt;1&gt;&gt;也是如此。

alice 类型比 bob 类型多得多。

因此没有从size_t 值集合到类型集合的注入。

因此,如果是双射的,则任何此类映射都必须是不完整的。您只能将某些类型的子集映射到 size_t 并再次映射回来。

您没有指定您只想处理类型的子集,所以答案是,您不能按照您的要求做。

请注意,std::type_index 只是一个可散列的、可比较的对象。它不是像整数值那样的“索引”。

【讨论】:

  • 这个参数无关紧要。任何有限程序只能实例化有限数量的类型。不要求分配的整数值在程序之间保持一致。因此,对于任何单个程序而言,按照提问者的要求创建从类型到整数的映射都没有与无穷大相关的障碍。
  • @eh9 我可以在单个程序中实例化多个size_t 类型。这将是一个带有小size_t 的系统上的大型程序,但它并不。假设 OP 只想将其限制为在当前程序或当前编译单元中声明的类型,或仅在当前编译单元中传递给实例化 encode_typedecode_types 的类型,这是不同的问题比OP问的要多。
  • 天使和针头。我假设提问者会对任何整数类型的映射感到满意,并且也会对程序总大小的限制感到满意。您的论点与在任意长度的输入上定义的形式语言与该语言的有限实现之间的普通关系没有什么不同。该实现始终是有限长度的某个子集的近似值。
【解决方案3】:

我将给出一个部分解决方案,因为我对解决所有杂乱的细节不感兴趣。使用一个宏,它接受一个包含整个类型定义的参数。现在用这个参数做三件事(需要有辅助宏):

  • 按原样使用参数来定义类型。
  • 将字符串化的参数传递给加密哈希函数。
    • 该函数的输出将是某个位数的整数。毫无疑问,因为在密码学上是安全的,所以该整数将大于内置的整数类型。因此,不要使用type_idsize_t 或诸如此类的东西,而是使用您自己的索引类型;它可以是 POD。
  • 该哈希函数的值是定义encode_typedecode_type 的神奇值。
    • 由于模板参数类型的限制,您可能必须使用保存哈希值的constexpr存储地址来定义decode_type

这里的基本原理与哥德尔证明他的不完备性定理的原理相同,即一串字符总是有两种解释,第一种是形式系统的成员,第二种是数字。 (顺便说一句,证明的核心是创建一种从正式系统中谈论该数字的方式。)

可以肯定的是,使用 C++ 工具链中现成的工具的细节很混乱。然而,使之成为可能的关键元素是字符串化运算符#。这类似于上面的第二种解释,即定义不是作为语言的成员,而是作为“只是数据”。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-23
    • 2014-05-20
    相关资源
    最近更新 更多