15

通常, constexpr 必须没有副作用。但是,我刚刚发现可以在抛出异常的构造函数中使用副作用。该技术可用于模拟 constexpr 函数的 assert(),如下面的程序所示。

#include <iostream>
#include <cstdlib>
#include <stdexcept>

struct constexpr_precond_violated : std::logic_error
{
  constexpr_precond_violated(const char* msg) :
    std::logic_error(msg)
  {
    std::cerr << msg << '\n';
    abort(); // to get a core dump
  }
};

#define TO_STRING_IMPL(x) #x
#define TO_STRING(x) TO_STRING_IMPL(x)

#define CONSTEXPR_PRECOND(cond, value) \
  ((!(cond)) ? throw constexpr_precond_violated( \
    "assertion: <" #cond "> failed (file: " \
    __FILE__ ", line: " TO_STRING(__LINE__) ")")    \
   : (value))

constexpr int divide(int x, int y)
{
  return CONSTEXPR_PRECOND(y != 0, x / y);
}

int main(int argc, char** argv)
{
  // The compiler cannot know argc, so it must be evaluated at runtime.
  // If argc is 2, the precondition is violated.
  return divide(100, argc - 2);
}

我用 g++ 4.7.2 和 clang++ 3.1 对其进行了测试。当先决条件失败时,您将获得错误位置和核心转储。

./constexpr_assert some_arg
assertion: <y != 0> failed (file: constexpr_assert.cpp, line: 26)
Aborted (core dumped)

所以它适用于当前的编译器,但它是合法的 C++11 吗?

4

1 回答 1

14

这是合法的。

对于每个constexpr函数,必须有一些导致常量表达式的参数值(第 7.1.5/5 节):

对于一个constexpr函数,如果不存在函数参数值使得函数调用替换会产生一个常量表达式(5.19),那么程序是非良构的;无需诊断。

请注意,这并不意味着每个可能的参数值都必须产生一个常量表达式。divide显然有一些导致常量表达式的参数值:divide(1, 1)是一个简单的例子。所以,这个定义显然是有效的。

但是可以divide(1, 0)叫吗?是的,它可以。constexpr调用函数或“普通”函数(第 7.1.5/7 节)几乎没有区别:

对函数的调用在所有方面都constexpr与对等价的非函数的调用产生相同的结果,只是对函数的调用可以出现在常量表达式中。constexprconstexpr

请注意,对constexpr函数的调用可以出现在常量表达式中,但没有什么可以阻止它们不产生常量表达式。这是为了让人们可以调用constexpr带有编译时和运行时参数的函数(否则有用性constexpr将受到限制)。

为了完整起见,让我们看看是什么构成了常量表达式(§5.19/2):

条件表达式核心常量表达式,除非它涉及以下之一作为潜在评估的子表达式(第 3.2 节),但逻辑与(第 5.14 节)、逻辑或(第 5.15 节)和条件(第 5.16 节)操作的子表达式未评估的不被视为 [...]。

所以,divide(1, 1)是一个常量表达式,但divide(1, 0)不是。如果您divide(1, 0)在模板参数中使用,则程序格式错误。但除此之外还好。

于 2012-10-23T21:58:17.330 回答