g ++不会使用assert编译constexpr函数

Vit*_*meo 9 c++ assert g++ constexpr c++11

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); 
}
Run Code Online (Sandbox Code Playgroud)

错误: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++在不使用宏的情况下编译代码吗?

Yak*_*ont 13

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));
}
Run Code Online (Sandbox Code Playgroud)

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

第一次,因为我们希望有一个assert,何时true,可以从一个constexpr函数调用.第二,所以我们可以将两个函数链接成一个constexpr函数.

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

assert_helper存在,因为内容assert是当实现定义NDEBUG是真实的,所以我们不能把它嵌入到一个表达式(它可能是一个声明,而不是一个表达式).这也保证了失败的constexpr_assert失败是constexpr即使assertconstexpr(比方说,如果NDEBUG是假的).

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

  • @Potatoswatter不,不是.当'NDEBUG`未定义时,`assert(test)`是`void(0)`,你的表达式是非法的.当定义`NDEBUG`时,`assert(test)`是完全实现定义的.所以`test && assert(test)`可能是也可能不是有效的代码,可能有效也可能无效. (2认同)
  • 不需要单独的函数`assert_helper`:C++ 11 19.3/2描述了头文件`<cassert>`:"内容与标准C库头`<assert.h>`相同.参见:ISO C 7.2." N1570(差不多是C11)7.2.1.1/2状态:"`assert`宏将诊断测试放入程序中;它扩展为void表达式." 所以`getClamped`的主体可以简单地是`return assert(mMin <mMax),(mValue <mMin?mMin :( mValue> mMax?mMax:mValue));`. (2认同)
  • @KyleStrand这就是我作为交叉编译器c ++ 11解决方案提出的问题https://gist.github.com/oliora/928424f7675d58fadf49c70fdba70d2f (2认同)

Kyl*_*and 5

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

有三个缺点:

  • 正如您的问题所述,这在C++ 11中不起作用.
  • assert意志,当然,从来没有在编译时触发.即使添加static_assert具有相同条件的a也不起作用,因为mMin并且mMax不被视为常量表达式.
  • 此外,由于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.
    • 同样,这不会针对发布版本编译出来(除非你用s 包装quick_exit调用ifdef).
  • 将一个实际assert内部包装在一个lambda中,该lambda传递给一个带有任意可调用的结构(作为模板参数)并调用它然后调用std::quick_exit,然后调用throw该结构.这个似乎是严重的过度杀伤,但当然它会在运行时生成一个真正的断言失败消息,这很好.
    • 这是唯一不会导致发布版本崩溃的方法.
    • oliora提供了这种方法变体,没有throwquick_exit.这似乎更清洁,更健全.