G++ 不编译带有断言的 constexpr 函数
g++ doesn't compile constexpr function with assert in it
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
也不起作用,因为mMin
和mMax
不被视为常量表达式。 - 此外,由于
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
;发布版本将触发失败和崩溃。
- Niebler 在他的帖子中没有提到这一点,但
- 抛出其构造函数调用
std::quick_exit
的自定义表达式类型。这消除了对nothrow
的需求。- 同样,这不会为发布版本编译出来(除非您将
quick_exit
调用包装在ifdef
的中)。
- 同样,这不会为发布版本编译出来(除非您将
- 将实际
assert
包装在 lambda 中,该 lambda 被传递给一个结构,该结构接受任意可调用对象(作为模板参数)并调用它,然后调用std::quick_exit
,然后throw
该结构。这似乎是严重的矫枉过正,但当然它会在运行时生成真正的断言失败消息,这很好。- 这是不会导致发布版本崩溃的唯一方法。
- Oriora提供了这种方法的变体,没有
throw
和quick_exit
。这似乎更干净、更理智。
g++ 是对的。根据标准,constexpr
语句中不允许使用非静态assert
。
。其功能主体应为复合语句,其中仅包含:
空语句,
static_assert声明,
不定义类或枚举的 typedef 声明和别名声明,
使用声明,
using-指令,
正好是一个返回语句。
-- 7.1.5/3
constexpr 在编译时进行计算。运行时中的非静态断言。
- 条件constexpr函数
- constexpr 函数中的非文字(通过 std::is_constant_evaluated)
- constexpr 函数获取常量字符*
- 如何在 constexpr 函数中实现回退运行时
- 在非 constexpr 函数中作为左值传递的变量上使用 'constexpr' 函数
- NVCC 错误:string_view.h:constexpr 函数返回是非常量
- constexpr函数中的静态constexpr变量
- 在 constexpr 函数中断言
- G++ 编译器是否在未使用返回值的情况下将 constexpr 函数视为常规函数?
- constexpr 函数的常量引用参数:gcc/msvc vs clang/icc
- constexpr 函数在编译时获取值,即使我的变量不是 constexpr
- 如何正确地对 constexpr 函数进行单元测试
- 为什么 std::launder 是一个 constexpr 函数?
- 我可以使用 constexpr 函数声明一个静态数组吗?
- 用于连接向量的 Constexpr 函数
- 在非constexpr函数上添加的constexpr限定符不会触发任何警告
- 为什么 std::get<T> 其中 T 是调用 constexpr 函数失败的结果?
- 在constexpr函数中插入许多模板
- 在enable_if_t中调用 constexpr 函数
- constexpr 函数中的 for 循环无法使用 MSVC 19.23 进行编译