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

4

4 回答 4

13

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

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

我们两次滥用逗号运算符。

第一次是因为我们想要一个assertthat, when true, 可以从一个constexpr函数中调用。第二个,所以我们可以将两个函数链接成一个constexpr函数。

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

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

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

于 2013-09-06T01:13:55.540 回答
5

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

有三个缺点:

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

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

简而言之,策略是:

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

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

...它的函数体应该是一个复合语句,仅包含:
  空语句、
  static_assert-declarations、
  typedef 声明和不定义类或枚举的别名声明、
  使用声明、
  使用指令,
  并且只有一个返回陈述。
      -- 7.1.5/3

于 2013-09-06T00:42:12.687 回答
1

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

于 2016-10-06T21:25:27.343 回答