在我看来,拥有一个“总是返回5的函数”破坏或稀释了“调用函数”的意义。必须有一个原因,或者需要这个功能,否则它就不会出现在c++ 11中。为什么会在那里?

// preprocessor.
#define MEANING_OF_LIFE 42

// constants:
const int MeaningOfLife = 42;

// constexpr-function:
constexpr int MeaningOfLife () { return 42; }

在我看来,如果我写一个函数,返回一个字面值,然后我进行代码检查,有人会告诉我,我应该声明一个常量值,而不是返回5。


当前回答

这里的许多回答似乎有些相反,或者把安静的部分大声说出来,把吵闹的部分小声说出来。关于constexpr你需要知道的一件关键的事情是:

// This guarantees only that the value of "MeaningOfLife" can not be changed
// from the value calculated on this line by "complex_initialization()"
// (unless you cast away the const of course, don't do that).
// Critically here, everything happens at *runtime*.
const int MeaningOfLife = complex_initialization(1234, 5678, "hello");
// This guarantees that "MeaningOfLife" is fully evaluated and "initialized"
// *at compile time*.  If that is not possible due to complex_initialization()
// not being evaluatable at compile time, the compiler is required to abort
// compilation of the program.
// Critically here, to put a fine point on it, everything happens at
// *compile time*, guaranteed.  There won't be a runtime call to
// complex_initialization() at all in the final program.
constexpr int MeaningOfLife = complex_initialization(1234, 5678, "hello");

注意,是左边的常量使保证有存在的理由。当然,这取决于你是否能确保右边的值在编译时被求出来,重要的是,仅仅声明一个函数constexpr本身并不能做到这一点。

因此,您的问题的答案是,当您需要或希望它的初始化(右边发生的所有事情)完全在编译时发生或中断构建时,您应该声明一个变量constexpr。

其他回答

曾经有一种元编程模式:

template<unsigned T>
struct Fact {
    enum Enum {
        VALUE = Fact<T-1>*T;
    };
};

template<>
struct Fact<1u> {
    enum Enum {
        VALUE = 1;
    };
};

// Fact<10>::VALUE is known be a compile-time constant

我相信引入constexpr是为了让你编写这样的构造,而不需要模板和带有特化的奇怪构造,SFINAE之类的东西——但就像你编写一个运行时函数一样,但保证结果将在编译时确定。

但是,请注意:

int fact(unsigned n) {
    if (n==1) return 1;
    return fact(n-1)*n;
}

int main() {
    return fact(10);
}

用g++ -O3编译它,你会看到事实(10)确实在编译时被求值了!

一个VLA-aware编译器(C99模式下的C编译器或带有C99扩展的c++编译器)甚至可以允许你做:

int main() {
    int tab[fact(10)];
    int tab2[std::max(20,30)];
}

但目前它是非标准的c++ - constexpr看起来是一种解决这个问题的方法(即使没有VLA,在上面的例子中)。还有一个问题,就是需要有“正式的”常量表达式作为模板参数。

简介

引入Constexpr并不是为了告诉实现可以在需要常量表达式的上下文中求值;一致性实现在c++ 11之前就已经证明了这一点。

实现无法证明的是某段代码的意图:

开发人员想用这个实体表达什么? 我们是否应该盲目地允许在常量表达式中使用代码,仅仅因为它碰巧有效?


没有警察,这个世界将会怎样?

假设您正在开发一个库,并意识到您希望能够计算区间(0,N]中每个整数的和。

int f (int n) {
  return n > 0 ? n + f (n-1) : n;
}

缺乏意图

如果在转换过程中传递的参数是已知的,编译器可以很容易地证明上述函数在常量表达式中是可调用的;但你并没有宣布这是一个意图——这只是碰巧的情况。

现在另一个人来了,读取你的函数,做和编译器一样的分析;“哦,这个函数在常量表达式中可用!”,然后写了下面的代码。

T arr[f(10)]; // freakin' magic

优化

作为一个“了不起的”库开发人员,您决定f应该在被调用时缓存结果;谁会想要一遍又一遍地计算同一组值呢?

int func (int n) { 
  static std::map<int, int> _cached;

  if (_cached.find (n) == _cached.end ()) 
    _cached[n] = n > 0 ? n + func (n-1) : n;

  return _cached[n];
}

结果

通过引入愚蠢的优化,您破坏了在需要常量表达式的上下文中对函数的所有使用。

您从未承诺过函数在常量表达式中是可用的,没有constexpr,就无法提供这样的承诺。


为什么我们需要constexpr?

constexpr的主要用途是声明意图。

如果一个实体没有被标记为constexpr——它从未打算在常量表达式中使用;即使是这样,我们也依赖编译器来诊断这样的上下文(因为它忽略了我们的意图)。

例如std::numeric_limits<T>::max():不管出于什么原因,这是一个方法。Constexpr在这里很有用。

另一个例子:你想声明一个与另一个数组一样大的c数组(或std::array)。目前的做法是这样的:

int x[10];
int y[sizeof x / sizeof x[0]];

但如果能这样写不是更好吗:

int y[size_of(x)];

感谢constexpr,你可以:

template <typename T, size_t N>
constexpr size_t size_of(T (&)[N]) {
    return N;
}

何时使用constexpr:

只要有编译时间常数。

这里的许多回答似乎有些相反,或者把安静的部分大声说出来,把吵闹的部分小声说出来。关于constexpr你需要知道的一件关键的事情是:

// This guarantees only that the value of "MeaningOfLife" can not be changed
// from the value calculated on this line by "complex_initialization()"
// (unless you cast away the const of course, don't do that).
// Critically here, everything happens at *runtime*.
const int MeaningOfLife = complex_initialization(1234, 5678, "hello");
// This guarantees that "MeaningOfLife" is fully evaluated and "initialized"
// *at compile time*.  If that is not possible due to complex_initialization()
// not being evaluatable at compile time, the compiler is required to abort
// compilation of the program.
// Critically here, to put a fine point on it, everything happens at
// *compile time*, guaranteed.  There won't be a runtime call to
// complex_initialization() at all in the final program.
constexpr int MeaningOfLife = complex_initialization(1234, 5678, "hello");

注意,是左边的常量使保证有存在的理由。当然,这取决于你是否能确保右边的值在编译时被求出来,重要的是,仅仅声明一个函数constexpr本身并不能做到这一点。

因此,您的问题的答案是,当您需要或希望它的初始化(右边发生的所有事情)完全在编译时发生或中断构建时,您应该声明一个变量constexpr。