在constexpr抛出的异常中使用副作用是否合法?

Phi*_*ßen 15 c++ assert language-lawyer constexpr c++11

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

我用g ++ 4.7.2和clang ++ 3.1测试了它.当前提条件失败时,您将获得错误位置和核心转储.

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

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

R. *_*des 14

这是合法的.

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

对于constexpr函数,如果不存在函数参数值,使得函数调用替换将产生常量表达式(5.19),则程序格式错误; 无需诊断.

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

但可以divide(1, 0)叫吗?是的,它可以.调用constexpr函数或"正常"函数(第7.1.5/7节)几乎没有区别:

constexpr函数的调用产生与constexpr在所有方面调用等效非函数相同的结果,除了对constexpr函数的调用可以出现在常量表达式中.

请注意,对constexpr函数的调用可以出现在常量表达式中,但没有任何条件禁止它们不会导致常量表达式.这是为了能够constexpr使用编译时和运行时参数调用函数(否则有用性constexpr会受到限制).

为了完整性,让我们看看是什么使得常量表达式(§5.19/ 2):

条件表达式是一个核心常量表达式除非它涉及以下作为一个潜在的评价子表达式(§3.2)中的一个,但逻辑与(§5.14),逻辑OR(§5.15),和条件子表达式(§5.16)操作未评估的未被考虑[...].

所以,divide(1, 1)是一个不变的表达,但divide(1, 0)事实并非如此.如果您divide(1, 0)在模板参数中使用,则程序将是格式错误的.但除此之外没关系.