在我看来,拥有“总是返回5的函数”正在破坏或削弱“调用函数”的含义。一定有原因或需要此功能,否则在C ++ 11中就不会存在。为什么在那儿?

// preprocessor.
#define MEANING_OF_LIFE 42

// constants:
const int MeaningOfLife = 42;

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


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

评论

您可以定义返回constexpr的递归函数吗?如果是这样,我可以看到用法。

我认为问题应该指出“如果编译器可以自己推断出函数是否可以在编译时求值,为什么要引入一个新关键字(!)”。使其具有“由关键字保证”听起来不错,但我想我希望在不需要关键字的情况下尽可能保证它。
@Kos:可能更喜欢C ++内部知识的人可能会喜欢您的问题,但是我的问题来自一个曾经写过C代码但根本不熟悉C ++ 2011关键字或C ++编译器实现细节的人。能够推理出编译器优化和常量表达式推论是一个比这个问题更高级的用户问题。

@Kos我一直在和您一起思考,而我的答案是,没有constexpr,您如何(轻松地)知道编译器实际上为您编译了函数评估时间?我想您可以检查程序集的输出以查看其效果,但是只告诉编译器您需要这种优化比较容易,并且如果由于某种原因它不能为您完成优化,它将为您提供一个不错的编译效果-错误,而不是默默地未能优化您期望的优化位置。

@Kos:您可以对const说同样的话。实际上,强制意图很有用!数组维就是一个典型的例子。

#1 楼

假设它做了一些复杂的事情。
constexpr int MeaningOfLife ( int a, int b ) { return a * b; }

const int meaningOfLife = MeaningOfLife( 6, 7 );

现在,您可以将某些东西评估为一个常数,同时保持良好的可读性,并且比将常数设置为一个数字还可以进行更复杂的处理。 br />随着您的工作变得越来越明显,它基本上为维护性提供了良好的帮助。以max( a, b )为例:
template< typename Type > constexpr Type max( Type a, Type b ) { return a < b ? b : a; }

那里是一个非常简单的选择,但它的确意味着如果使用常量值调用max,它将在编译时而不是在运行时显式计算。例如DegreesToRadians函数。每个人都觉得度数比弧度更容易读。虽然您可能知道180度的弧度是3.14159265(Pi),但它写得更加清楚,如下所示:
/ w / cpp / language / constexpr

评论


很好的一点是它告诉编译器在编译时尝试计算值。我很好奇为什么在指定特定的优化后const不提供此功能?还是呢?

– TamusJRoyce
2011年1月20日14:37



@Tamus:通常,但不是必须的。 constexpr强制编译器执行,如果无法执行,则会吐出错误。

–戈斯
2011年1月20日14:38



我现在看到了。 Sin(0.5)是另一个。这样可以很好地替换C宏。

– Warren P
2011年1月20日在21:19



我可以将其视为一个新的采访问题:解释const和constexpr关键字之间的区别。

– Warren P
2011年1月20日21:30

为了自己记录这一点,我反复编写了与上述类似的代码,但函数为“ const”而不是“ constexpr”。当我使用Clang3.3,-pedantic-errors和-std = c ++ 11时,我希望后者无法编译。它按照“ constexpr”的情况进行编译和运行。您是否认为这是clang扩展,或者自从回答了这篇文章以来,对C ++ 11规范进行了调整吗?

– Arbalest
13年7月24日在22:44



#2 楼

简介

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在被调用时应将结果缓存;谁又想一次又一次地计算相同的值集?函数的所有用法恰好在需要常量表达式的上下文中。

您从未保证函数可以在常量表达式中使用,并且如果没有constexpr,就不可能提供这样的承诺。


那么,为什么我们需要constexpr

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

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

评论


这可能是正确的答案,因为C ++ 14和C ++ 17的最新更改允许在constexpr表达式中使用更大范围的语言。换句话说,几乎所有东西都可以用constexpr注释(也许有一天它会因为这个而消失吗?),除非有人对何时使用constexpr做出判断,否则几乎所有代码都将被编写。因此。

– alecov
16年7月29日在16:11

@alecov绝对不是所有内容... I / O,系统调用和动态内存分配都不能定义为constexpr此外,并非所有内容都应为constexpr。

–徐家好
18-10-8在7:52

@alecov某些函数应在运行时执行,而在编译时这样做是没有意义的。

–徐家好
18-10-8在7:53



我也最喜欢这个答案。编译时评估是一种巧妙的优化,但是constexpr的真正保证是某种行为的保证。就像const一样。

–TomášZato-恢复莫妮卡
19年3月14日在10:09

什么编译器允许此无constexpr版本的int f(int n){return n> 0? n + f(n-1):n;} T arr [f(10)];我不能在任何地方编译它?

–耶
4月15日18:15



#3 楼

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

另一个示例:您要声明一个与另一个数组一样大的C数组(或std::array)。目前执行此操作的方式如下:
int x[10];
int y[sizeof x / sizeof x[0]];


感谢constexpr,您可以:

int y[size_of(x)];


评论


@Kos:否。它将返回运行时值。 constexpr强制编译器使函数返回编译时值(如果可以)。

–deft_code
2011年1月20日16:00

@Kos:如果没有constexpr,则无论函数调用的结果是否为编译时常量,都不能在数组大小声明中使用它,也不能将其用作模板参数。基本上,这两个是constexpr的唯一用例,但至少模板参数用例很重要。

–康拉德·鲁道夫(Konrad Rudolph)
2011年1月20日下午16:11

“出于某种原因,这是一个方法”:原因是C ++ 03中只有编译时间整数,而没有其他编译时间类型,因此只有一种方法可以用于C ++ 11之前的任意类型。

–塞巴斯蒂安·马赫
2012年8月6日上午10:38

@LwCui不,这不是“好的”:默认情况下,GCC在某些方面是松懈的。使用-pedantic选项,它将被标记为错误。

–康拉德·鲁道夫(Konrad Rudolph)
16年8月17日在12:53



@SexyBeast不确定您的意思吗?在编译时知道int大小,在编译时知道常数10,因此在编译时也知道数组大小,在运行时什么也没有“调用”

– paulm
16年11月18日在11:56

#4 楼

constexpr函数确实非常不错,并且是c ++的重要补充。但是,您说对了,因为它解决的大多数问题都可以通过宏很好地解决。 />
// 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;


评论


您能否说明“极端细微的链接器错误”?或至少提供澄清的指针?

– enobayram
13年7月24日在14:34

@enobayram,三元运算符获取操作数的地址。从代码中看不出来。一切都可以正常编译,但是链接失败,因为四个地址无法解析。我必须真正弄清楚是谁在获取我的静态const变量的地址。

–deft_code
13年7月24日在19:48

“这有明显的原因是不好的”:最明显的原因是分号,对吗?

– TonyK
13年7月28日在20:50

“极端细微的链接器错误”使我完全困惑。范围都不是四个或五个。

–陆even
13年8月30日在23:30

另请参见新的枚举类类型,它修复了一些枚举问题。

– ninMonkey
2013年9月5日,下午2:53

#5 楼

根据我的阅读,对constexpr的需求来自元编程中的一个问题。特性类可能具有表示为函数的常量,请考虑:numeric_limits :: max()。使用constexpr,这些类型的函数可以用于元编程或用作数组边界等。定义自己的常量以进行某些操作。

编辑:

反复研究SO之后,似乎其他人都提出了一些有关constexprs可能实现的示例。

评论


“要成为接口的一部分,您必须成为一个函数”?

–丹尼尔(Daniel Earwicker)
2011年1月20日14:24

现在,我已经看到了这样做的用处,我对C ++ 0x感到更加兴奋。似乎是经过深思熟虑的事情。我知道他们一定是。那些语言标准的超级极客很少做随机的事情。

– Warren P
2011年1月20日在21:24

我对lambda,线程模型,initializer_list,rvalue引用,可变参数模板,新的绑定重载感到更加兴奋……还有很多值得期待的地方。

– luke
2011年1月20日23:29

哦,是的,但我已经了解其他几种语言中的lambda / closures。 constexpr在具有强大的编译时表达式评估系统的编译器中特别有用。 C ++在该领域确实没有同行。 (这是对C ++ 11,恕我直言的强烈赞誉)

– Warren P
2011年11月10日16:17



#6 楼

摘自Stroustrup在“ Going Native 2012”上的讲话:

评论


该示例也可以在Stroustrup的论文《基础设施软件开发》中找到。

–马修·波尔特(Matthieu Poullet)
13年7月24日在7:29

clang-3.3:错误:constexpr函数的返回类型'Value '不是文字类型

– Mitja
13年8月26日在6:23

很好,但是谁将文字放在这样的代码中。如果要编写交互式计算器,让编译器为您“检查单位”将很有意义。

– bobobobo
2013年12月16日下午4:02

@bobobobo,或者如果您正在为“火星气候轨道器”编写导航软件,也许:)

–杰里米·弗里斯纳(Jeremy Friesner)
16 Jan 18'在3:40



要使其编译-1.在文字后缀中使用下划线。 2.将运算符“” _m添加为100_m。 3.使用100.0_m,或添加一个接受unsigned long long的重载。 4.声明值构造函数constexpr。 5.像这样将对应的运算符/添加到Value类:constexpr自动运算符/((const Value &other)const { :: kg-Value :: TheUnit :: kg,TheUnit :: s-Value :: TheUnit :: s >>(val / other.val); }。其中TheUnit是在Value类内添加的Unit的typedef。

–0kcats
16 Dec 9'在18:44



#7 楼

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

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


#8 楼

曾经有一种使用元编程的模式:

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)确实在编译时被散发了!或具有C99扩展名的C ++编译器)甚至可以允许您执行以下操作:

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


但目前它是非标准的C ++-constexpr看起来像一种解决此问题的方法(在上述情况下,即使没有VLA)。仍然存在需要将“形式”常量表达式作为模板参数的问题。

评论


在编译时不评估事实函数。它必须是constexpr,并且只能有一个return语句。

–总结
2011年7月19日在17:08

@Sumant:您是对的,它不必在编译时进行评估,但事实是!我指的是编译器中真正发生的事情。在最近的GCC上编译它,查看生成的asm,如果您不相信我,请自己检查!

–科斯
2011年7月19日在18:50



尝试添加std :: array ,您会看到在编译时未评估fact()。只是GCC优化器做得很好。

–user283145
2012年2月15日14:12

那就是我说的...我真的不清楚吗?见最后一段

–科斯
2012-02-15 18:46

#9 楼

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


评论


同样可以这样写:const size_t MaxIPV4StringLength = sizeof(“ 255.255.255.255”);

– Superfly Jon
17年1月16日在12:02



静态内联constexpr const auto可能更好。

–徐家好
18-10-8在7:54

#10 楼

所有其他答案都很不错,我只想举一个很酷的例子,说明您可以用constexpr做到的一件令人惊奇的事情。 See-Phit(https://github.com/rep-movsd/see-phit/blob/master/seephit.h)是一个编译时HTML解析器和模板引擎。这意味着您可以放入HTML并取出可以操纵的树。在编译时完成解析可以为您带来一些额外的性能。

从github页面示例:

#11 楼

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

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


over

int arr[5];


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

#12 楼

它可以启用一些新的优化。传统上,const是类型系统的提示,不能用于优化(例如,const成员函数可以合法地仍然可以const_cast并修改对象,因此const不能被优化)。表示表达式确实是常量,前提是该函数的输入为const。考虑:

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


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

添加const

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


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

评论


实际上const可以用于优化...即使在const_cast IIRC之后,修改值定义的const也是未定义的行为。我希望它对于const成员函数而言是一致的,但是我需要使用标准进行检查。这意味着编译器可以在那里安全地进行优化。

–科斯
2011-1-20 23:27



@Warren:优化是否实际完成并不重要,只是允许这样做。 @Kos:鲜为人知的微妙之处在于,如果未将原始对象声明为const(int x与const int x),则可以通过const_cast修改它-在指针/引用上删除const是安全的。否则,const_cast将始终调用未定义的行为,并且是无用的:)在这种情况下,编译器没有有关原始对象的const-ness的信息,因此无法分辨。

– AshleysBrain
2011-1-21在1:59



@Kos我不认为const_cast是这里唯一的问题。允许const方法读取甚至修改全局变量。相反,来自另一个线程的人也可以在两次调用之间修改const对象。

– enobayram
13年7月24日在14:47

“ = 0”在此处无效,应将其删除。我会自己做,但是我不确定这是否符合SO协议。

–KnowItAllWannabe
2014年1月24日,3:15

这两个示例均无效:第一个示例(int GetNumber()const = 0;)应该将GetNumber()方法声明为虚方法。第二个(constexpr int GetNumber()const = 0;)无效,因为纯说明符(= 0)表示该方法是虚拟的,但是constexpr的方法不能是虚拟的(ref:en.cppreference.com/w/cpp / language / constexpr)

–stj
16 Mar 4 '16 at 10:22

#13 楼

何时使用constexpr:何时有编译时间常数。



评论


虽然我同意您的观点,但这个答案并不能解释为什么constexpr应该比预处理器宏或const更受欢迎。

–Sneftel
19年6月13日在12:31

#14 楼

它对于诸如

// constants:
const int MeaningOfLife = 42;

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

int some_arr[MeaningOfLife()];



这样的东西很有用。

评论


在您的示例中,与普通常量相比,它提供了零优势,因此它并不能真正回答问题。

–杰夫
2011年1月20日14:48

这是一个人为的示例,想象一下MeaningOfLife()是否从其他地方获取值,例如另一个函数或#define或series therof。您可能不知道它返回什么,它可能是库代码。在其他示例中,设想一个具有constexpr size()方法的不可变容器。您现在可以执行int arr [container.size()];

– plivesey
2011年1月21日,11:49

@plivesey能否请您用一个更好的示例来编辑您的答案。

– Mukesh
17年9月20日在6:14