【问题标题】:Compile-time assertion?编译时断言?
【发布时间】:2011-07-20 17:13:08
【问题描述】:

有没有办法可以断言两个常量表达式在编译时相等?

例如我希望这会导致编译时错误

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 };
}

我的文件.h:

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

【问题讨论】:

    标签: c++ compiler-construction


    【解决方案1】:

    是的。您可以使用 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>
    }
    

    代码基本上来自记忆,可能需要一些调整。

    【讨论】:

    • 你也可以不用assert()成员,直接使用默认构造函数:StaticAssert&lt;1==1&gt;();
    • 既然模板采用bool,为什么不能传递任何在运行时以编程方式创建的布尔值?我正在尝试你的这个新颖的代码并传入一个我在运行时更新的布尔值,但编译器失败说non-type template argument is not a constant expression
    • 模板在编译时评估(和生成),因此必须使用编译器在编译时已知的值(constexpr
    • 这是我正在寻找的实现,谢谢。很干净。这是另一个参考:drdobbs.com/compile-time-assertions/184401873 和 Alexandrescu 的 Modern C++ Design book 第 2.1 章。
    【解决方案2】:

    static_assert(仅限C++0x);如果在旧版本上,请参阅Boost's StaticAssert

    【讨论】:

    • 我也没有 Boost。这是一个没有这种奢侈品的嵌入式系统。我很幸运我有 C++ 而不仅仅是普通的 C。
    • @Jason:它是一个嵌入式系统并不重要(除非你的 compiler 在嵌入式系统上运行......我希望不是)——断言是静态的,所以它显然没有编译到最终代码中。 :-) 如果您愿意,请从 Boost 库中提取此部分。
    • 根据 OP 的使用情况,如果比较是在变量的内容之间,static_assert 可能不起作用。
    【解决方案3】:

    对于静态断言的另一个版本,您可以通过添加更好的名称来美化,您可以使用:

    // 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 之前的编译器上使用这些技巧。
    • 如果你使用 -Werror=unused-local-typedefs 那么使用这个 STATIC_ASSERT 会有问题。模板解决方案似乎是一个更好的选择。
    【解决方案4】:

    Windows 的另一种可能性是C_ASSERT,如果包含 Windows.h,则定义它。

    【讨论】:

    • 我不想被束缚在 Windows 上——但是您发布的链接显示了它是如何实现的,而且非常简单,非常感谢!
    【解决方案5】:

    还有使用switch (..) 语句的技巧。不过有点老套路。案例条目 foo == bar 必须在编译时进行评估,如果它碰巧为假,则 switch 语句将导致错误。编译器也会将其减少为“无”。

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

    【讨论】:

      【解决方案6】:

      您可以这样定义自己的静态断言:

      #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;
      }
      

      【讨论】:

        【解决方案7】:

        类似于 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}’的成员

        你不需要 typedef。没有:

        “VALUES_ARE_EQUAL”不是“VALUES”的成员

        当然,还有很多其他方法可以生成有用的消息。对于咯咯笑:

        // 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]
        

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

        【讨论】:

          【解决方案8】:
          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 成语。

          【讨论】:

          • ummmm... 这如何回答我的问题? (“有没有一种方法可以断言两个常量表达式在编译时相等?”)
          • 你是对的。我通过将checked_delete 替换为更具体的功能来编辑答案。然而,这个想法保持不变。而且...您可以轻松地将测试表达式 a == b 更改为其他内容,例如测试三个枚举是否都相等(a == b &amp;&amp; b == c)或测试枚举是否在一个范围内((a - b) &lt; N)等。
          【解决方案9】:

          你可以做一些预处理魔法,比如

          #define FOO_VALUE 263
          #define BAR_VALUE 264
          
          enum {foo=FOO_VALUE, bar=BAR_VALUE}
          
          #if !(FOO_VALUE == BAR_VALUE)
          #error "Not equal"
          #endif
          

          【讨论】:

          • 在我的特殊情况下,我不能依赖它——其中一个枚举来自我无法控制的代码。
          • 所以只依赖#if#error#endif。这为您提供了您所要求的确切内容,这是对 X::foobar 是否彼此相等的编译时测试。 #error 的好处是 you 控制错误消息。不太好的事情是一些 C 预处理器不符合 ANSI,尤其是用于古代嵌入式系统的预处理器。
          • 我很乐意,但预处理器只能访问#define-d 常量,而不是枚举。
          【解决方案10】:

          我会选择一个可用的 static_asserts。

          • boost::static_assert
          • C++0x 静态断言

          但只是因为我在写这个之前从未尝试过:

          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 #error 是预处理器指令,在编译阶段不可用。
          【解决方案11】:

          我建议看一下 Eigen 库静态断言机制:

          http://eigen.tuxfamily.org/dox/StaticAssert_8h_source.html

          【讨论】:

            【解决方案12】:
            template<int X, int Y>
            struct Check
            {
              enum { value = false };
            };
            
            template<int X>
            struct Check<X,X>
            {
              enum { value = true };
            };
            

            我以int 为例。您可以根据需要进行更改。 Here is the demo。 用法:

            Check<foo, bar>::value
            

            【讨论】:

            • 不幸的是,这不起作用。这是一个运行时测试,看看值是否不相等,你想要的是一个编译时测试。
            • @Martin,是的,正确。 OP 要求断言,我已经指出了查找值是否相等的方法。当前标准中更好的方法是boost::static_assert
            • 那么你应该在你的答案中提到 boost::static_assert 。我认为他已经知道如何在运行时测试两件事是否相等(这是一项相对微不足道的任务)。我是在指出您误读了这个问题。
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2011-08-08
            • 2010-11-02
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多