G++ 不编译带有断言的 constexpr 函数

g++ doesn't compile constexpr function with assert in it

本文关键字:constexpr 函数 断言 编译 G++      更新时间:2023-10-16
template<typename T> constexpr inline 
T getClamped(const T& mValue, const T& mMin, const T& mMax) 
{ 
     assert(mMin < mMax); // remove this line to successfully compile
     return mValue < mMin ? mMin : (mValue > mMax ? mMax : mValue); 
}

错误:constexpr 函数 'constexpr T getClamped(const T&, const T&, const T&) [with T = long unsigned int]' 的主体不是返回语句

使用 g++ 4.8.1 . clang++ 3.4不抱怨。

谁在这里?有什么方法可以在不使用宏的情况下g++编译代码?

GCC是对的。 但是,有一个相对简单的解决方法:

#include "assert.h"
inline void assert_helper( bool test ) {
  assert(test);
}
inline constexpr bool constexpr_assert( bool test ) {
  return test?true:(assert_helper(test),false);
}
template<typename T> constexpr
inline T getClamped(const T& mValue, const T& mMin, const T& mMax)
{
  return constexpr_assert(mMin < mMax), (mValue < mMin ? mMin : (mValue > mMax ? mMax : mValue));
}

我们滥用逗号运算符的地方,两次。

第一次是因为我们希望有一个assert,当true时,可以从constexpr函数调用。 第二个,因此我们可以将两个函数链接为一个constexpr函数。

附带的好处是,如果无法验证constexpr_assert表达式在编译时是否true,则getClamped函数不会constexpr

assert_helper之所以存在,是因为assert的内容是在NDEBUG为 true 时定义的实现,因此我们不能将其嵌入到表达式中(它可以是语句,而不是表达式)。 它还保证即使constexpr失败assert constexpr_assert也无法constexpr(例如,当NDEBUG为假时)。

所有这一切的缺点是,您的断言不是在出现问题的行处触发,而是在更深的 2 个调用处触发。

截至 C++14,这不再是问题; 带有 -std=c++14 标志g++可以很好地编译和运行您的代码。

有三个缺点:

  • 如您的问题中所述,这在 C++11 中不起作用
  • 当然,assert永远不会在编译时触发。即使添加具有相同条件的static_assert也不起作用,因为mMinmMax不被视为常量表达式。
  • 此外,由于assert不是在编译时触发的,而是constexpr函数,如果条件为 false,但在编译时计算表达式(例如 constexpr auto foo = getClamped(1,2,0); ),assert永远不会触发 - 这意味着不会捕获不正确的函数参数。

在评论中,用户oliora链接到Eric Niebler的一篇有趣的博客文章,该文章描述了在C++11中起作用多种方法,并且可以在编译时或运行时根据需要触发。

简而言之,这些策略是:

  • throw例外;要使其无法捕获(即更像assert),请将constexpr功能标记为nothrow
    • Niebler 在他的帖子中没有提到这一点,但throw表达式必须包含在某种更大的逻辑表达式中,只有在assert ed 的条件是 false 时才被评估,例如三元表达式(这是 Niebler 在他的示例中使用的)。即使在 C++14 中,也不允许使用独立的 if (condition) throw <exception>; 声明。
    • Niebler也没有注意到,与assert不同,这种方法依赖于NDEBUG;发布版本触发失败和崩溃。
  • 抛出其构造函数调用 std::quick_exit自定义表达式类型。这消除了对nothrow的需求。
    • 同样,这不会为发布版本编译出来(除非您将quick_exit调用包装在 ifdef 的中)。
  • 将实际assert包装在 lambda 中,该 lambda 被传递给一个结构,该结构接受任意可调用对象(作为模板参数)并调用它,然后调用 std::quick_exit ,然后throw该结构。这似乎是严重的矫枉过正,但当然它会在运行时生成真正的断言失败消息,这很好。
    • 这是不会导致发布版本崩溃的唯一方法。
    • Oriora提供了这种方法的变体,没有throwquick_exit。这似乎更干净、更理智。

g++ 是对的。根据标准,constexpr语句中不允许使用非静态assert

。其功能主体应为复合语句,其中仅包含:
  空语句,
  static_assert声明,
  不定义类或枚举的 typedef 声明和别名声明,
  使用声明,
  using-指令,
  正好是一个返回语句。
      -- 7.1.5/3

constexpr 在编译时进行计算。运行时中的非静态断言。