编译时检查特征专长是否具有唯一ID

时间:2018-10-04 10:51:59

标签: c++ c++11

我看过很多文章,解释了如何为一个类生成唯一的ID。

在我的情况下,ID是由用户选择的(出于各种原因),但是我想确保在不同的类中没有两次使用过ID。

我将问题简化为以下代码:

struct A {}; struct B {};

template <typename T> struct traits {};
template <> struct traits<A> { static constexpr size_t id() { return 0; }}
template <> struct traits<B> { static constexpr size_t id() { return 1; }}

现在,我有一种简单的方法来确保没有人添加带有重复ID的特质:

struct C {};
template <> struct traits<C> { static constexpr size_t id() { return 1; // this should static_assert ! }}

我可以使用C ++ 11,并且我不想滥用预处理器。

如果可能的话,该解决方案应该不需要专用于特征的代码中的任何特殊内容(即,如果可以通过查看已经存在的专业来从外部进行检查,那就太好了。)

谢谢

2 个答案:

答案 0 :(得分:3)

这是一个主意,我不确定它对您是否适用:

#include <cstddef>

struct A {}; struct B {}; struct C {};

template <size_t Id> constexpr size_t getId() { return Id; }

template <typename T> struct traits {};

template size_t getId<0>();
template <> struct traits<A> { static constexpr size_t id() { return getId<0>(); }};

template size_t getId<1>();
template <> struct traits<B> { static constexpr size_t id() { return getId<1>(); }};

/* This fails to compile
template size_t getId<1>();
template <> struct traits<C> { static constexpr size_t id() { return getId<1>(); }};
*/

int main() { return 0; }

这依赖于使用函数的显式模板实例化。显然,陷阱在于您可能忘记添加此实例化并仍然使用该函数,然后它就不会失败。您可以定义一些预处理器宏以确保始终使用其定义特征。


编辑:Oliv指出,以上解决方案仅在所有模板实例化发生在同一转换单元中时才有效。此版本可在所有编译单元上使用,尽管它更容易出错(模板参数和返回值必须匹配)。

#include <cstddef>

struct A {}; struct B {}; struct C {};

template <size_t Id> constexpr size_t getId();

template <typename T> struct traits {};

template <> constexpr size_t getId<0>() { return 0; }
template <> struct traits<A> { static constexpr size_t id() { return getId<0>(); }};

template <> constexpr size_t getId<1>() { return 1; }
template <> struct traits<B> { static constexpr size_t id() { return getId<1>(); }};

/* This fails to compile
template <> constexpr size_t getId<1>() { return 1; }
template <> struct traits<C> { static constexpr size_t id() { return getId<1>(); }};
*/

int main() { return 0; }

编辑2:由于发布此答案时发现令人惊讶的行为,我发布了问题Why does explicit template instantiation not break ODR?。有关无法编译的内容的详细信息,请参见此处。

答案 1 :(得分:1)

这听起来很糟糕,但并不完美,但是您可以通过从串联的__LINE____FILE__宏定义中计算一些哈希函数来强制实现非常好的唯一性。

constexpr std::uint32_t calculate_hash(std::uint32_t seed, const char data[])
{
    //some hashing algorithm
}

#define CALCULATE_HASH_FROM_LINE() calculate_hash(__LINE__, __FILE__) 

struct A {};
struct B {};
struct C {};

template <typename T> struct traits {};

template <> struct traits<A> { static constexpr size_t id() { return CALCULATE_HASH_FROM_LINE(); }}; 
template <> struct traits<B> { static constexpr size_t id() { return CALCULATE_HASH_FROM_LINE(); }};
template <> struct traits<C> { static constexpr size_t id() { return CALCULATE_HASH_FROM_LINE(); }};

完整代码:http://coliru.stacked-crooked.com/a/c1805baf9863b238

优点:

  • 符合标准
  • 易于使用,可能难以破解

缺点:

  • 在一行中定义两件事将失败
  • 根本无法使用模板

其他方法是使用宏提取类型名称:

constexpr std::uint32_t calculate_hash(std::uint32_t seed, const char data[])
{
    //some hashing algorithm
}

#define CALCULATE_HASH(T) calculate_hash(29, #T) 

struct A {};
struct B {};
struct C {};

template <typename T> struct traits {};

template <> struct traits<A> { static constexpr size_t id() { return CALCULATE_HASH(A); }}; 
template <> struct traits<B> { static constexpr size_t id() { return CALCULATE_HASH(B); }};
template <> struct traits<C> { static constexpr size_t id() { return CALCULATE_HASH(C); }};

完整代码:http://coliru.stacked-crooked.com/a/46b00f9aea039c5b 此方法具有与以前相同的优缺点,但另外:

优点:

  • 与以前的方法相比,与实际类型的关联更多

缺点:

  • 更多信息传递给宏
  • “特征”的专业化必须包含所需类型的完整名称空间(这也意味着所有专业化可能都应放在一个名称空间中)。否则可能会导致名称冲突。
  • ::A将产生与A
  • 不同的哈希

因此,这两种方式在许多情况下都可能有效,但本质上是错误的。 C ++非常需要编译时反射:/.