什么时候应该在C ++ 11中使用constexprfunction?

在我看来,有一个“总是返回5的函数”正在打破或淡化“调用函数”的含义。 必须有一个原因,或需要这个能力,否则它不会在C ++ 11中。 为什么呢?

// preprocessor. #define MEANING_OF_LIFE 42 // constants: const int MeaningOfLife = 42; // constexpr-function: constexpr int MeaningOfLife () { return 42; } 

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

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

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

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

另一个很好的例子是DegreesToRadians函数。 每个人都觉得度数比弧度更容易阅读。 虽然你可能知道180度是弧度的,但它更清晰,如下所示:

 const float oneeighty = DegreesToRadians( 180.0f ); 

很多很好的信息在这里:

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

介绍

constexpr并没有作为一种方式来告诉实现,在需要一个常量expression的上下文中可以评估某些东西; 一致的实现已经能够在C ++ 11之前certificate这一点。

某个实现无法certificate的是某个代码片段的意图

  • 开发者想要用这个实体expression什么?
  • 我们是否应该盲目地让代码被用在一个常量expression式中 ,只是因为它恰好工作?

没有constexpr ,世界会变成什么constexpr

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

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

缺乏意图

编译器可以很容易地certificate,如果在转换过程中传递的参数是已知的,则上述函数可以在常量expression式中调用; 但是你还没有把它当作一个意图 – 事情就是这样。

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

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

优化

作为一个“很棒的”图书馆开发者,你决定f应该在调用时caching结果; 谁会想重复计算相同的一组值?

 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]; } 

结果

通过引入你愚蠢的优化,你只是打破了你的函数的每一个用法,恰巧在需要一个常量expression式的上下文中。

你从来没有答应这个函数可以用在一个常量expression式中 ,没有constexpr就没有办法提供这样的承诺。


那么,为什么我们需要constexpr

constexpr的主要用途是声明意图

如果一个实体没有被标记为constexpr那么这个实体永远不会被用在一个常量expression式中 。 即使是这样,我们依靠编译器来诊断这样的上下文(因为它不理睬我们的意图)。

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 ++之外。 但是,你所解决的大部分问题都可以用macros来解决。

但是, constexpr其中一个用途是没有C ++ 03等效的types常量。

 // 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; 

从我读过的东西,需要constexpr来自元编程的一个问题。 特征类可能有常量表示为函数,认为:numeric_limits :: max()。 用constexpr,这些types的函数可以用于元编程,或者用作数组边界等等。

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

编辑:

在SO之后,看起来其他人已经拿出了一些 constexprs可能的例子 。

来自Stroustrup在“走向本土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 fp literal suffixed by 's' { return Value<Second> (d); } constexpr Value<Second2> operator"" s2(long double d) // a fp 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 

另一个用途(尚未提到)是constexpr构造函数。 这允许创build不需要在运行时期间初始化的编译时间常量。

 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的编译器(C99模式下的C编译器或C99扩展中的C ++编译器)甚至可以让您执行:

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

但目前它是非标准的C ++ – constexpr看起来像是一种对抗这种方式的方法(即使没有VLA,在上面的例子中)。 还有一个问题是需要将“正式的”常量expression式作为模板参数。

刚刚开始将项目切换到C ++ 11,并遇到了constexpr的完美情况,它清除了执行相同操作的替代方法。 这里的关键是,只有在声明为constexpr时,才能将该函数放入数组大小声明中。 在很多情况下,我可以看到这是非常有用的,并且涉及到我所涉及的代码领域。

 constexpr size_t GetMaxIPV4StringLength() { return ( sizeof( "255.255.255.255" ) ); } void SomeIPFunction() { char szIPAddress[ GetMaxIPV4StringLength() ]; SomeIPGetFunction( szIPAddress ); } 

你的基本例子与常数本身的论点一样。 为什么使用

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

过度

 int arr[5]; 

因为它更方便维护。 使用constexpr要比现有的元编程技术写得更快,读取速度更快。

它可以启用一些新的优化。 const传统上是types系统的一个提示,不能用于优化(例如,一个const成员函数可以const_cast和修改对象无论如何,合法,所以const不能被信任优化)。

constexpr表示expression式确实是常量,只要函数的input是const。 考虑:

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

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

添加constexpr

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

编译器现在可以应用一个优化,其中GetNumber()的返回值被caching,并消除对GetNumber()额外调用,因为constexpr是一个更强有力的保证,返回值不会改变。

这对于类似的东西很有用

 // constants: const int MeaningOfLife = 42; // constexpr-function: constexpr int MeaningOfLife () { return 42; } int some_arr[MeaningOfLife()]; 

将其与特质类或类似的东西绑在一起,变得非常有用。