【问题标题】:How to dispatch between assert() and static_assert(), dependend if in constexpr context?如果在 constexpr 上下文中,如何在 assert() 和 static_assert() 之间分派,依赖?
【发布时间】:2020-05-09 19:32:40
【问题描述】:

在 C++11 constexpr 函数中,第二条语句(例如 assert())是不可能的。 static_assert() 很好,但如果该函数被称为“普通”函数,它将不起作用。逗号运算符可以来帮助 wrto。 assert(),但很丑陋,一些工具会发出警告。

考虑这样的“getter”,它在断言旁边是完全可解释的。但我想为运行时和编译时保留某种断言,但不能仅仅根据“constexpr”上下文重载。

template<int Size>
struct Array {
  int m_vals[Size];
  constexpr const int& getElement( int idx ) const
  {
    ASSERT( idx < Size ); // a no-go for constexpr funcs in c++11
    // not possible, even in constexpr calls as being pointed out, but what I would like:
    static_assert( idx < Size, "out-of-bounds" );
    return m_vals[idx];
  }
};

附带条件:C++11、无堆、无异常、无编译器细节。

注意正如评论者指出的(谢谢!),static_assert 的论点是不可能的(但会很好)。在这种情况下,编译器给了我一个关于越界访问的不同错误。

【问题讨论】:

  • "但很丑" 嗯,逗号操作符可能很丑,但它在 C++11 中可以胜任。花哨的解决方案是切换到 C++14。 :)
  • "一些工具会发出警告" 哪些工具和哪些警告?
  • 逗号运算符曾经是我的解决方案,但是 a) 被 qacpp(编码指南)等静态代码分析警告,b) 在下游项目中导致奇怪的语法错误(没有理解,怀疑自定义断言宏)。好吧,我现在只是尽量避免它,但我同意它确实起到了作用。
  • @Borph 不,您根本不能使用依赖于idxstatic_assert。如果函数用于需要常量表达式的上下文中,则只能通过强制评估使其不是常量表达式的构造来诊断 idx 的错误值。在这样的上下文之外,您永远无法在编译时检查该值。

标签: c++ c++11 assert constexpr static-assert


【解决方案1】:

类似

void assert_impl() { assert(false); } // Replace body with own implementation

#ifdef NDEBUG // Replace with own conditional
#define my_assert(condition) ((void)0)
#else
#define my_assert(condition) ((condition) ? (void()) : (assert_impl(), void()))
#endif

template<int Size>
struct Array {
  int m_vals[Size];
  constexpr const int& getElement( int idx ) const
  {
    return my_assert(idx < Size), m_vals[idx];
  }
};

如果在上下文中使用需要一个常量表达式(因为它将调用非constexpr函数),它将在断言失败时给出编译时错误。

否则它将在运行时调用assert(或您的类似物)失败。

据我所知,这是您能做的最好的事情。没有办法使用idx 的值在编译时强制检查上下文需要 常量表达式。

逗号运算符语法不好,但C++11 constexpr 函数非常有限。

当然,正如您已经指出的,如果在需要常量表达式的上下文中使用函数,无论如何都会诊断出未定义的行为。

如果您知道assert(或您的类似物)在条件计算结果为true 时不会扩展为常量表达式中禁止的任何内容,但如果计算结果为false 则这样做,那么您可以直接使用它而不是 my_assert 并跳过我在代码中构建的间接。

【讨论】:

  • 鉴于投反对票,有人可以向我解释我的答案哪里错了吗?
  • 这和@ecatmur 的解决方案类似,我只能选择一个答案。你的直截了当。但是有一个评论:为什么(void)0NDEBUG 案例中,而void() 在另一个案例中?还是真的一样?
  • @Borph (void)0 是一个空操作,它编译为空(这是你在定义 NDEBUG 时想要的),而你需要 void() 以便第二个和第三个条件运算符的操作数具有相同的类型,void
  • @Borph 我认为(void)0 在所有情况下都可以。我只是在第一种情况下替换了它,因为 void() 也可以根据上下文被解析为不带参数且不带返回类型的函数类型。在第二种情况下的子表达式中不能这样解析。
【解决方案2】:

static_assert 不能在这里使用。 constexpr 函数的参数不允许在常量表达式中使用。因此,在给定的约束条件下,您的问题没有解决方案

但是,我们可以通过弯曲两个约束来解决问题

  1. 不使用static_assert(使用其他方法来生成编译时诊断),并且

  2. 忽略逗号运算符“很丑,一些工具会发出警告。” (显示其丑陋是C++11 constexpr函数严格要求的不幸结果)

那么,我们可以使用普通的assert

template <int Size>
struct Array {
  int m_vals[Size];
  constexpr const int& getElement(int idx) const
  {
    return assert(idx < Size), m_vals[idx];
  }
};

在常量评估上下文中,这将发出类似error: call to non-'constexpr' function 'void __assert_fail(const char*, const char*, unsigned int, const char*)' 的编译器错误。

【讨论】:

    【解决方案3】:

    比逗号表达式更好的是,您可以使用三元条件。第一个操作数是您的断言谓词,第二个操作数是您的成功表达式,并且由于第三个操作数可以是任何表达式 - 即使是在 C++11 常量上下文中不可用的表达式 - 您可以使用 lambda 调用库的 @987654321 @设施:

    #define ASSERT_EXPR(pred, success)    \
        ((pred) ?                         \
         (success) :                      \
         [&]() -> decltype((success))     \
         {                                \
             ASSERT(false && (pred));     \
             struct nxg { nxg() {} } nxg; \
             return (success);            \
         }())
    

    lambda 主体的解释:

    • ASSERT(false &amp;&amp; (pred)) 是为了确保使用适当的表达式(用于字符串化)调用您的断言机制。
    • struct nxg { nxg() {} } nxg 是为了未来安全,以确保如果您在 C++17 或更高版本中使用 NDEBUG 编译,则 lambda 仍然不是constexpr,因此断言在 const-evaluation 上下文中强制执行。
    • return (success) 有两个原因:确保第二个和第三个操作数具有相同的类型,并且如果您的库尊重 NDEBUG,则无论 pred 是什么,都会返回 success 表达式。 (pred 将被评估,但您希望断言谓词的评估成本低且没有副作用。)

    使用示例:

    template<int Size>
    struct Array {
      int m_vals[Size];
      constexpr int getElement( int idx ) const
      {
        return ASSERT_EXPR(idx < Size, m_vals[idx]);
      }
    };
    
    constexpr int I = Array<2>{1, 2}.getElement(1); // OK
    constexpr int J = Array<2>{1, 2}.getElement(3); // fails
    

    【讨论】:

    • 啊! @walnut 和一个未初始化的变量,如果不被访问,以后可能会被允许。我会努力寻找更好的后卫。谢谢!
    • 你是对的,pred 评估应该很便宜,但并非总是如此。所以作为一般的ASSERT_EXPR 宏,我不推荐。我有时会在自己的断言中进行昂贵的调用(例如检查不变量)。
    • @ecatmur 为什么需要struct nxg { nxg() {} } nxg? C++17 会出现什么问题?它允许更多的constexpr 函数,你的意思是assert 也将是constexpr
    • @Borph 我假设即使您打开NDEBUG 以禁用运行时断言,您仍然希望检查编译时断言。使失败案例 lambda 的主体非constexpr 是确保这一点的一种方法 - 但它确实需要在运行时评估和丢弃NDEBUG 中的谓词。否则,您可以将 NDEBUG 下的宏定义为 return (success);
    猜你喜欢
    • 1970-01-01
    • 2016-09-16
    • 2016-11-13
    • 2014-08-15
    • 2020-01-07
    • 1970-01-01
    • 2020-01-08
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多