编译时断言?

35
有没有办法在编译时断言两个常量表达式相等呢?
例如,我希望这会导致编译时错误。
enum { foo=263, bar=264 };
SOME_EXPRESSION(foo,bar)

但是我希望这不会引起错误。
enum { foo=263, bar=263 };
SOME_EXPRESSION(foo,bar)

编辑:上面的内容有所简化。我的情况更像是这样的。

some_other_file_I_dont_control.h:

class X
{
public:
   enum { foo=263 };
}

my_file.h:

enum { bar=something+somethingelse }; // bar should equal X::foo
SOME_EXPRESSION(X::foo, bar)
12个回答

38

可以。你可以使用 bool 类型的模板特化来实现,例如:

// empty default template
template <bool b>
struct StaticAssert {};

// template specialized on true
template <>
struct StaticAssert<true>
{
    static void assert() {}
};

int f()
{
    StaticAssert<1==1>::assert();   // compiles fine, assert() member found
    StaticAssert<1==2>::assert();   // compile failure, no assert() member for StaticAssert<false>
}

代码基本上是来自内存,可能需要一些微调。


3
您也可以省略assert()成员,只使用默认构造函数:StaticAssert<1==1>(); - Julien-L
由于模板接受了一个bool,为什么不能传递在运行时被程序创建的任何布尔值呢?我正在尝试您的新代码,并传递我在运行时更新的布尔值,但编译器失败并显示非类型模板参数不是常量表达式 - johnbakers
2
模板在编译时进行评估(和生成),因此必须使用编译器在编译时已知的值(constexpr)。 - Chad
2
这就是我要找的实现,非常干净。谢谢。这里还有另一个参考资料:http://www.drdobbs.com/compile-time-assertions/184401873 和Alexandrescu的《现代C++设计》第2.1章。 - KlingonJoe

36

2
而且我也没有Boost。这是一个嵌入式系统,没有那么奢侈的东西。我很幸运能够使用C++而不仅仅是纯C。 - Jason S
8
@Jason:除非你的编译器在嵌入式系统上运行,否则这并不重要(希望不是这样)——断言是静态的,所以显然不会编译到最终代码中。 :-) 如果需要,可以从Boost库中提取此部分内容。 - user541686
根据发帖者的用途,“static_assert”可能无法在变量内容之间进行比较。 - johnbakers

24

你可以使用另一种静态断言的版本,并通过添加更好的名称来使其更为优美:

// name must be a valid identifier
#define STATIC_ASSERT( condition, name )\
    typedef char assert_failed_ ## name [ (condition) ? 1 : -1 ];

然后使用:

STATIC_ASSERT( x == y, constants_must_be_same );

编译器会触发类似以下错误:

size of array 'assert_failed_constants_must_be_same' is negative

这似乎不是很有帮助,但它将指向断言的确切行,过一段时间后,您将开始将该错误消息处理为静态断言失败


禁止使用负数大小的数组比模板版本更容易记住。如果你必须在早于C++11的编译器上使用这样的技巧。 - Wolf
2
如果你使用-Werror=unused-local-typedefs,那么使用此STATIC_ASSERT将是一个问题。模板解决方案似乎是更好的选择。 - blue_whale

6

还有一个技巧是使用switch(..)语句。这种方法有点老式了。case项中的foo==bar必须在编译时进行评估,如果结果为false,则switch语句将导致错误。编译器也会将其简化为“无”。

{ 
  bool x=false; 
  switch (x) {
  case foo == bar:
    break;
  case false:
    // Compile time test that foo == bar
    break;
}

6

Windows 可能使用的另一种方法是C_ASSERT,如果包含了Windows.h,则会定义该方法。


2
我不想被绑定在Windows上 - 但你发布的链接展示了它是如何实现的,而且非常简单,所以谢谢! - Jason S

2

您可以自定义静态断言,方法如下:

#include <iostream>
template <bool b> class ClassStaticAssert;
template <>
class ClassStaticAssert<true>{static const bool value = true;};
#define STATIC_ASSERT(e) (ClassStaticAssert<e>())
int main()
{
    STATIC_ASSERT(0);
    return 0;
}

1

与iammillind的解决方案类似,不幸的是他的解决方案只在运行时有用:

template <int A, int B>
class VALUES { 
};

// specialization to provide safe passage for equal values        
template <int X>
class VALUES<X, X> {
public:
   static void MY_VALUES_ARE_EQUAL() {}
};


#define ASSERT_EQUALITY(a, b)    \
{    \
   typedef VALUES<a, b> COMPILE_TIME_ASSERTION;       \
   COMPILE_TIME_ASSERTION::VALUES_ARE_EQUAL();     \
}

int main() {
   ASSERT_EQUALITY(1, 1);    // compiles just fine
   ASSERT_EQUALITY(1, 2);    // ERROR!
   // . . . 
 }

这个好处是它提供了一个漂亮的编译器消息。我的编译器告诉我以下内容:

‘VALUES_ARE_EQUAL’不是‘COMPILE_TIME_ASSERTION {aka VALUES<1, 2>}’的成员

你不需要typedef。没有typedef时:

'VALUES<1, 2>'的成员中没有'VALUES_ARE_EQUAL'

当然,还有其他许多生成有用消息的方法。开心一点:
// these give use some tips in the compiler warnings
class COMPILE_TIME_EQUALITY_ASSERTION {} compiler_message; 
class EQUAL_VALUES_ONLY_PLEASE {};


template <int A, int B>
class VALUES {
public:
   static void AreEqual(EQUAL_VALUES_ONLY_PLEASE) {}
};

template <int X>
class VALUES<X, X>
{
public:
   static void AreEqual(COMPILE_TIME_EQUALITY_ASSERTION) {}
};


#define ASSERT_EQUALITY(a, b)                                   \
{                                                               \
   VALUES<a, b>::AreEqual(compiler_message);                             \
}

int main() {
    ASSERT_EQUALITY(1, 1) // a-okay
    ASSERT_EQUALITY(1, 2) // ERROR!
}

我收到以下编译器错误:
no matching function for call to:
‘VALUES<1,2>::AreEqual(COMPILE_TIME_EQUALITY_ASSERTION&)' 
candidate is:
static void VALUES<\A, B>::AreEqual(EQUAL_VALUES_ONLY_PLEASE) [with int A = 1, int B = 2]

静态成员函数/构造函数/字段赋值/隐私和模板规范的组合可能会产生不同的结果,也许更适合您的情况。

1
template <int a, int b>
inline void static_assert_equal()
{
    typedef char enum_values_must_be_equal[a == b ? 1 : -1];
    (void) sizeof(enum_values_must_be_equal);
}

int main()
{
    enum { foo = 1, bar = 2, fum = foo };
    static_assert_equal<foo, fum>(); // compiles ok
    static_assert_equal<foo, bar>(); // fails at compile time
    return 0;
}

这来自于checked_delete惯用语。

那这怎么回答我的问题呢?(“有没有一种方法可以在编译时断言两个常量表达式相等?”) - Jason S
你说得对。我通过替换checked_delete为一个更具体的函数来编辑了答案。然而,思路仍然是一样的。而且... 你可以轻松地将测试表达式a == b更改为其他内容,例如测试三个枚举是否都相等(a == b && b == c)或测试枚举是否在范围内((a - b) < N)等。 - Andreas Spindler

0
你可以进行一些预处理器的魔法,例如:
#define FOO_VALUE 263
#define BAR_VALUE 264

enum {foo=FOO_VALUE, bar=BAR_VALUE}

#if !(FOO_VALUE == BAR_VALUE)
#error "Not equal"
#endif

在我的情况下,我不能依赖于它 - 其中一个枚举来自我无法控制的代码。 - Jason S
1
所以只依赖于 #if#error#endif。这将为您提供精确的编译时测试,即 X::foobar 是否相等。#error 的好处在于 可以控制错误消息。不太好的一点是,一些 C 预处理器不符合 ANSI 标准,特别是对于古老的嵌入式系统的预处理器。 - David Hammen
2
我很想这样做,但是预处理器只能访问 #define 定义的常量,而不能访问枚举类型。 - Jason S

0

我会选择其中一个可用的 static_asserts。

  • boost::static_assert
  • C++0x static_assert

但只是因为我以前从未尝试过,所以我写了这个:

enum { foo=263, bar=264 };

template<bool test>
struct CompileAssert
{
    bool assert() {}
};

template<>
struct CompileAssert<false>  {}; // fail on false.

int main()
{
    CompileAssert<foo != bar>().assert();  // Now I have seen Chad above I like his static 
    CompileAssert<foo == bar>().assert();  // method better than using a normal method.
}                                          // But I tried zero length arrays first did 
                                           // not seem to work

如果可用#error,则更加紧凑且错误消息可以变得非常明显。boost::static_assert有点臃肿,而且错误消息也很臃肿++。 - David Hammen
@David Hammen #error是预处理器指令,在编译阶段不可用。 - Captain Obvlious

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接