简体   繁体   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); 
}

error : body of constexpr function 'constexpr T getClamped(const T&, const T&, const T&) [with T = long unsigned int]' not a return-statement 错误constexpr函数主体 'constexpr T getClamped(const T&,const T&,const T&)[with T = long unsigned int]' not return-statement

Using g++ 4.8.1 . 使用g++ 4.8.1 clang++ 3.4 doesn't complain. clang++ 3.4不抱怨。

Who is right here? 谁在这? Any way I can make g++ compile the code without using macros? 我可以用g++编译代码而不使用宏吗?

GCC is right. GCC是对的。 However, there is a relatively simple workaround: 但是,有一个相对简单的解决方法:

#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));
}

where we abuse the comma operator, twice. 我们滥用逗号运算符的地方,两次。

The first time because we want to have an assert that, when true , can be called from a constexpr function. 第一次因为我们想要一个assert ,当为true ,可以从constexpr函数调用。 The second, so we can chain two functions into a single constexpr function. 第二个,所以我们可以将两个函数链接到一个constexpr函数中。

As a side benefit, if the constexpr_assert expression cannot be verified to be true at compile time, then the getClamped function is not constexpr . 作为附带好处,如果在编译时无法验证constexpr_assert表达式为true ,则getClamped函数不是constexpr

The assert_helper exists because the contents of assert are implementation defined when NDEBUG is true, so we cannot embed it into an expression (it could be a statement, not an expression). assert_helper存在是因为当NDEBUG为true时, assert的内容是实现定义的,所以我们不能将它嵌入到表达式中(它可以是一个语句,而不是一个表达式)。 It also guarantees that a failed constexpr_assert fails to be constexpr even if assert is constexpr (say, when NDEBUG is false). 它还保证即使assertconstexpr (例如,当NDEBUG为false时), constexpr_assert失败也不能成为constexpr

A downside to all of this is that your assert fires not at the line where the problem occurs, but 2 calls deeper. 所有这一切的缺点是你的断言不是在发生问题的行发射,而是2次调用更深。

As of C++14, this is no longer an issue; 从C ++ 14开始,这不再是一个问题; g++ with the -std=c++14 flag compiles and runs your code just fine. 带有-std=c++14标志的g++编译并运行你的代码就好了。

There are three drawbacks: 有三个缺点:

  • As noted in your question, this does not work in C++11. 正如您的问题所述,这在C ++ 11中不起作用
  • The assert will, of course, never by triggered at compile time. 当然, assert永远不会在编译时触发。 Even adding a static_assert with the same condition won't work, since mMin and mMax are not considered constant expressions. 即使添加具有相同条件的static_assert也不起作用,因为mMinmMax不被视为常量表达式。
  • Moreover, because the assert isn't triggered at compile time, but the function is constexpr , if the condition is false but the expression is evaluated at compile time (eg constexpr auto foo = getClamped(1,2,0); ), the assert will never fire--meaning that the incorrect function arguments will not be caught. 此外,由于assert未在编译时触发,但功能是constexpr ,如果条件为 ,但在编译时表达式进行求值(例如constexpr auto foo = getClamped(1,2,0); ),则assert 永远不会触发 - 意味着不会捕获不正确的函数参数。

In a comment, user oliora links to an interesting blog post by Eric Niebler that describes multiple approaches that work in C++11 and can be triggered while compiling or at runtime as appropriate. 在评论中,用户oliora链接到Eric Niebler撰写的一篇有趣的博客文章,其中描述了在C ++ 11中有效的多种方法, 并且可以在编译时或在运行时适当时触发。

In short, the strategies are: 简而言之,策略是:

  • throw an exception; throw异常; to make it uncatchable (ie more like an assert ), mark the constexpr function nothrow 使其无法捕获的(即更像是assert ),标志着constexpr函数nothrow
    • Niebler does not call this out in his post, but the throw expression must be wrapped in some kind of larger logical expression that is only evaluated if the condition being assert ed is false , such as a ternary expression (which is what Niebler uses in his example). Niebler并没有在他的帖子中调用它,但是throw表达式必须包含在某种更大的逻辑表达式中, 只有在被assert的条件为false才会被计算,例如三元表达式(这是Niebler在他的用法中使用的)例)。 A standalone if (condition) throw <exception>; 一个独立的if (condition) throw <exception>; statement will not be permitted, even in C++14. 声明将不会被允许,即使在C ++ 14。
    • Niebler also fails to note that, unlike assert , this approach does not depend on NDEBUG ; Niebler也没有注意到,与assert不同,这种方法依赖于NDEBUG ; release builds will trigger failures and crash. 发布版本触发失败和崩溃。
  • Throw a custom expression type whose constructor invokes std::quick_exit . 抛出一个自定义表达式类型,其构造函数调用std::quick_exit This eliminates the need for nothrow . 这消除了nothrow的需要。
    • Again, this won't be compiled out for release builds (unless you wrap the quick_exit call in ifdef 's). 同样,这不会被编译为发布版本(除非你在ifdef包装quick_exit调用)。
  • Wrap an actual assert inside a lambda, which is passed to a struct that takes an arbitrary callable (as a template parameter) and invokes it and then calls std::quick_exit , and then throw that struct. 在lambda中包装一个实际的assert ,它传递给一个带有任意可调用的结构(作为模板参数)并调用它,然后调用std::quick_exit ,然后throw该结构。 This one seems like severe overkill, but of course it generates a true assertion-failure message at runtime, which is nice. 这个似乎是严重的过度杀伤,但当然它会在运行时生成一个真正的断言失败消息,这很好。
    • This is the only approach that will not cause a release build to crash. 这是唯一不会导致发布版本崩溃的方法。
    • oliora provides a variation of this approach without the throw and the quick_exit . oliora提供了这种方法变体,没有throwquick_exit This seems much cleaner and saner. 这似乎更清洁,更健全。

constexpr calculates in compile time. constexpr在编译时计算。 Non static assert in run-time. 非静态断言在运行时。

g++ is right. g ++是对的。 Per the standard, a non-static assert is not permitted in a constexpr statement. 根据标准, constexpr语句中不允许使用非静态assert

... its function-body shall be a compound-statement that contains only: ...其函数体应为仅包含以下内容的复合语句:
null statements, 空语句,
static_assert-declarations, static_assert申述,
typedef declarations and alias-declarations that do not define classes or enumerations, typedef声明和不定义类或枚举的别名声明,
using-declarations, 使用申述,
using-directives, using指令,
and exactly one return statement. 而且只有一个退货声明。
-- 7.1.5/3 - 7.1.5 / 3

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

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