繁体   English   中英

g ++不会使用assert编译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]' not return-statement

使用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存在是因为当NDEBUG为true时, assert的内容是实现定义的,所以我们不能将它嵌入到表达式中(它可以是一个语句,而不是一个表达式)。 它还保证即使assertconstexpr (例如,当NDEBUG为false时), constexpr_assert失败也不能成为constexpr

所有这一切的缺点是你的断言不是在发生问题的行发射,而是2次调用更深。

从C ++ 14开始,这不再是一个问题; 带有-std=c++14标志的g++编译并运行你的代码就好了。

有三个缺点:

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

在评论中,用户oliora链接到Eric Niebler撰写的一篇有趣的博客文章,其中描述了在C ++ 11中有效的多种方法, 并且可以在编译时或在运行时适当时触发。

简而言之,策略是:

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

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

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

...其函数体应为仅包含以下内容的复合语句:
空语句,
static_assert申述,
typedef声明和不定义类或枚举的别名声明,
使用申述,
using指令,
而且只有一个退货声明。
- 7.1.5 / 3

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM