370

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

// preprocessor.
#define MEANING_OF_LIFE 42

// constants:
const int MeaningOfLife = 42;

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

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

4

14 回答 14

329

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

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

于 2011-01-20T14:27:38.360 回答
179

介绍

constexpr没有引入作为告诉实现可以在需要常量表达式的上下文中评估某些东西的方法;在 C++11 之前,符合标准的实现已经能够证明这一点。

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

  • 开发者想用这个实体表达什么?
  • 我们是否应该仅仅因为它恰好可以工作就盲目地允许在常量表达式中使用代码?

没有世界会constexpr怎样?

假设您正在开发一个库并意识到您希望能够计算区间中每个整数的总和(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];
}

结果

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

您从未承诺该函数可用于constant-expression,否则constexpr将无法提供此类承诺。


那么,我们为什么需要constexpr

constexpr的主要用途是声明意图

如果实体未标记为constexpr- 它从未打算用于常量表达式;即使是这样,我们也依赖编译器来诊断这种上下文(因为它无视我们的意图)。

于 2015-03-02T23:51:17.100 回答
94

采取std::numeric_limits<T>::max():无论出于何种原因,这是一种方法。constexpr在这里会很有用。

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

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;
}
于 2011-01-20T14:32:20.450 回答
20

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;
于 2011-01-20T16:20:05.850 回答
14

根据我的阅读,对 constexpr 的需求来自元编程中的一个问题。特征类可能具有表示为函数的常量,例如:numeric_limits::max()。使用 constexpr,这些类型的函数可用于元编程,或用作数组边界等。

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

编辑:

在探索了 SO 之后,看起来其他人已经提出了一些 使用 constexprs 可能实现的示例。

于 2011-01-20T14:21:44.847 回答
11

来自 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
于 2013-03-16T03:16:01.390 回答
9

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

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

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

3.14D + 42_i;
于 2011-01-20T18:16:59.637 回答
7

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

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

void SomeIPFunction()
{
    char szIPAddress[ GetMaxIPV4StringLength() ];
    SomeIPGetFunction( szIPAddress );
}
于 2014-02-04T07:02:23.580 回答
6

曾经有一种元编程模式:

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,在上述情况下)。并且仍然存在需要将“正式”常量表达式作为模板参数的问题。

于 2011-01-20T18:36:47.727 回答
4

所有其他答案都很棒,我只想举一个很酷的例子,说明你可以用 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;
}
于 2018-04-24T12:23:18.320 回答
1

您的基本示例与常量本身的论点相同。为什么使用

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

超过

int arr[5];

因为它更易于维护。与现有的元编程技术相比,使用 constexpr 的读写速度要快得多。

于 2011-01-20T16:23:52.437 回答
0

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

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

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

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

添加constexpr

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

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

于 2011-01-20T17:09:53.243 回答
-1

何时使用constexpr

  1. 只要有编译时间常数。
于 2019-06-13T08:58:48.547 回答
-3

它对类似的东西很有用

// constants:
const int MeaningOfLife = 42;

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

int some_arr[MeaningOfLife()];

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

于 2011-01-20T14:28:41.540 回答