在c++ 11中什么时候应该使用constexpr功能?

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

// preprocessor.
#define MEANING_OF_LIFE 42


// constants:
const int MeaningOfLife = 42;


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

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

141527 次浏览

据我所知,对constexpr的需求来自元编程中的一个问题。Trait类可能有常量表示为函数,例如:numeric_limits::max()。使用constexpr,这些类型的函数可以在元编程中使用,或者作为数组边界,等等。

我想到的另一个例子是,对于类接口,您可能希望派生类型为某些操作定义自己的常量。

编辑:

在SO上闲逛之后,其他人似乎已经提出了一些 例子,说明了可能使用constexpr实现的功能。

假设它做了一些更复杂的事情。

constexpr int MeaningOfLife ( int a, int b ) { return a * b; }


const int meaningOfLife = MeaningOfLife( 6, 7 );

现在您有了一些可以计算到一个常数的东西,同时保持良好的可读性,并允许稍微复杂一些的处理,而不仅仅是将一个常数设置为一个数字。

它基本上为可维护性提供了很好的帮助,因为它使您正在做的事情变得更加明显。例如max( a, b ):

template< typename Type > constexpr Type max( Type a, Type b ) { return a < b ? b : a; }

这是一个非常简单的选择,但这意味着如果你用常量值调用max,它是在编译时显式计算的,而不是在运行时。

另一个很好的例子是DegreesToRadians函数。每个人都觉得角度比弧度更容易读。虽然你可能知道180度是3.14159265 (Pi)弧度,但下面写得更清楚:

const float oneeighty = DegreesToRadians( 180.0f );

这里有很多好的信息:

http://en.cppreference.com/w/cpp/language/constexpr

它在某些方面很有用

// constants:
const int MeaningOfLife = 42;


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


int some_arr[MeaningOfLife()];

将它与特质类或类似的类联系起来,它会变得非常有用。

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函数真的很好,是对c++的一个很好的补充。但是,您是对的,它解决的大多数问题都可以用宏来解决。

然而,constexpr的一种用法没有c++ 03等效的类型化常量。

// This is bad for obvious reasons.
#define ONE 1;


// This works most of the time but isn't fully typed.
enum { TWO = 2 };


// This doesn't compile
enum { pi = 3.1415f };


// This is a file local lvalue masquerading as a global
// rvalue.  It works most of the time.  But May subtly break
// with static initialization order issues, eg pi = 0 for some files.
static const float pi = 3.1415f;


// This is a true constant rvalue
constexpr float pi = 3.1415f;


// Haven't you always wanted to do this?
// constexpr std::string awesome = "oh yeah!!!";
// UPDATE: sadly std::string lacks a constexpr ctor


struct A
{
static const int four = 4;
static const int five = 5;
constexpr int six = 6;
};


int main()
{
&A::four; // linker error
&A::six; // compiler error


// EXTREMELY subtle linker error
int i = rand()? A::four: A::five;
// It not safe use static const class variables with the ternary operator!
}


//Adding this to any cpp file would fix the linker error.
//int A::four;
//int A::six;

你的基本例子和常数本身的例子是一样的。为什么要使用

static const int x = 5;
int arr[x];

int arr[5];

因为这样更容易维护。使用constexpr写和读要比现有的元编程技术快得多。

它可以实现一些新的优化。const传统上是类型系统的提示,不能用于优化(例如,const成员函数可以合法地const_cast并修改对象,因此const不能被信任用于优化)。

constexpr表示表达式真的是常量,前提是函数的输入是const。考虑:

class MyInterface {
public:
int GetNumber() const = 0;
};

如果这在其他模块中公开,编译器不能相信GetNumber()不会在每次调用时返回不同的值——即使是连续的,中间没有非const调用——因为const可能已经在实现中被丢弃了。(显然,任何这样做的程序员都应该被枪毙,但语言允许这样做,因此编译器必须遵守这些规则。)

添加constexpr:

class MyInterface {
public:
constexpr int GetNumber() const = 0;
};

编译器现在可以应用优化,缓存GetNumber()的返回值,并消除对GetNumber()的额外调用,因为constexpr更强地保证了返回值不会改变。

另一个用途(尚未提及)是constexpr构造函数。这允许创建编译时常量,这些常量不必在运行时进行初始化。

const std::complex<double> meaning_of_imagination(0, 42);

将其与用户定义的字面量配对,就可以完全支持字面的用户定义类。

3.14D + 42_i;

曾经有一种元编程模式:

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编译它,你会看到fact(10)确实在编译时被求值了!

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

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

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

Stroustrup在“Going Native 2012”大会上的演讲如下:

template<int M, int K, int S> struct Unit { // a unit in the MKS system
enum { m=M, kg=K, s=S };
};


template<typename Unit> // a magnitude with a unit
struct Value {
double val;   // the magnitude
explicit Value(double d) : val(d) {} // construct a Value from a double
};


using Speed = Value<Unit<1,0,-1>>;  // meters/second type
using Acceleration = Value<Unit<1,0,-2>>;  // meters/second/second type
using Second = Unit<0,0,1>;  // unit: sec
using Second2 = Unit<0,0,2>; // unit: second*second


constexpr Value<Second> operator"" s(long double d)
// a f-p literal suffixed by ‘s’
{
return Value<Second> (d);
}


constexpr Value<Second2> operator"" s2(long double d)
// a f-p literal  suffixed by ‘s2’
{
return Value<Second2> (d);
}


Speed sp1 = 100m/9.8s; // very fast for a human
Speed sp2 = 100m/9.8s2; // error (m/s2 is acceleration)
Speed sp3 = 100/9.8s; // error (speed is m/s and 100 has no unit)
Acceleration acc = sp1/0.5s; // too fast for a human

刚刚开始将一个项目切换到c++11,遇到了一个非常好的constexpr情况,它清理了执行相同操作的替代方法。这里的关键点是,只有当函数声明为constexpr时,才能将其放入数组大小声明中。在许多情况下,我可以看到这在我所从事的代码领域非常有用。

constexpr size_t GetMaxIPV4StringLength()
{
return ( sizeof( "255.255.255.255" ) );
}


void SomeIPFunction()
{
char szIPAddress[ GetMaxIPV4StringLength() ];
SomeIPGetFunction( szIPAddress );
}

简介

引入constexpr并不是为了告诉实现可以在需要常数表达式;一致性实现在c++ 11之前就已经证明了这一点。

实现不能证明的是某段代码的意图:

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

没有constexpr世界将会怎样?

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

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

缺乏意图

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

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

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

优化

你,作为一个“awesome”一词库开发人员,决定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 -它从来没有打算在常数表达式中使用;即使是这样,我们也依赖编译器来诊断这样的上下文(因为它忽略了我们的意图)。

所有其他的答案都很棒,我只是想给一个很酷的例子,你可以用constexpr做一件很棒的事情。See-Phit (https://github.com/rep-movsd/see-phit/blob/master/seephit.h)是一个编译时HTML解析器和模板引擎。这意味着您可以放入HTML,然后取出能够操作的树。在编译时进行解析可以提供一些额外的性能。

从github页面的例子:

#include <iostream>
#include "seephit.h"
using namespace std;






int main()
{
constexpr auto parser =
R"*(
<span >
<p  color="red" height='10' >\{\{name}} is a \{\{profession}} in \{\{city}}</p  >
</span>
)*"_html;


spt::tree spt_tree(parser);


spt::template_dict dct;
dct["name"] = "Mary";
dct["profession"] = "doctor";
dct["city"] = "London";


spt_tree.root.render(cerr, dct);
cerr << endl;


dct["city"] = "New York";
dct["name"] = "John";
dct["profession"] = "janitor";


spt_tree.root.render(cerr, dct);
cerr << endl;
}

何时使用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有存在的理由。当然,由你来确保右边实际上可以在编译时被求值,重要的是,仅仅声明一个函数constexpr本身并不能做到这一点。

所以你的问题的答案是,当你需要或希望它的初始化(发生在右边上的一切)被强制完全在编译时发生或中断构建时,你应该声明一个变量constexpr