constexpr和const有什么区别?

833

constexprconst 有什么区别?

  • 我什么时候可以只使用其中一个?
  • 我什么时候可以同时使用它们,如何选择?

132
constexpr 创建编译时常量; const 仅表示该值不能被更改。 - David G
也许boost/hana库中的这篇文章可以阐明一些有关constexpr的问题,它会告诉你何时可以使用constexpr以及何时不能使用:https://www.boost.org/doc/libs/1_69_0/libs/hana/doc/html/index.html#tutorial-appendix-constexpr - Andry
1
@0x499602D2 "仅意味着该值无法更改" 对于使用文字初始化的标量,一个不可更改的值也是编译时常量。 - curiousguy
@curiousguy 是的,我的评论过于简单了。我承认当时我对constexpr也很陌生 :) - David G
@0x499602D2 const 的定义有点过载,尤其是在 C++ 中服务于不同的目的。如果当时有 constexprconst 可能会更单一用途。也许吧。谁知道呢?我们没有时间机器去旅行到另一个宇宙看看。 - curiousguy
10个回答

791

基本含义和语法

这两个关键字都可以用于对象和函数的声明。当应用于对象时,它们的基本区别如下:

  • const 将对象声明为常量。这意味着一旦初始化,该对象的值将不会改变,并且编译器可以利用这一事实进行优化。它还有助于防止程序员在初始化后修改不应被修改的对象。

  • constexpr 将对象声明为适用于标准所称的常量表达式。但请注意,constexpr 并不是唯一的方法。

当应用于函数时,基本区别如下:

  • const只能用于非静态成员函数,而不是一般的函数。它保证了成员函数不会修改任何非静态数据成员(除了可变数据成员,它可以被任意修改)。

  • constexpr可以用于成员函数和非成员函数,以及构造函数。它声明该函数适用于常量表达式。只有当函数满足某些条件(7.1.5/3,4)时,编译器才会接受它,其中最重要的是 (†)

    • 函数体必须是非虚拟的和极其简单的:除了typedefs和static asserts之外,只允许一个单独的return语句。对于构造函数,只允许初始化列表、typedefs和static assert。(= default= delete也是允许的。)
    • 从C++14开始,对于constexpr函数内部允许的内容更加宽松:asm声明、带有标签的goto语句、除了case和default之外的带有标签的语句、try块、非字面类型变量的定义、静态或线程存储期变量的定义、未执行初始化的变量的定义。
    • 参数和返回类型必须是字面类型(一般来说,非常简单的类型,通常是标量或聚合类型)。

常量表达式

如上所述,constexpr 声明对象和函数都适用于常量表达式。常量表达式不仅仅是常量:

  • 它可以在需要编译时评估的地方使用,例如模板参数和数组大小指定:

      template<int N>
      class fixed_size_list
      { /*...*/ };
    
      fixed_size_list<X> mylist;  // X 必须是一个整数常量表达式
    
      int numbers[X];  // X 必须是一个整数常量表达式
    
  • 但请注意:

  • 将某物声明为constexpr并不一定保证它会在编译时进行评估。它可以用于这样的情况,但也可以用于其他在运行时进行评估的地方。

  • 一个对象在没有声明为constexpr的情况下可能适用于常量表达式。例如:

         int main()
         {
           const int N = 3;
           int numbers[N] = {1, 2, 3};  // N 是常量表达式
         }
    

    这是可能的,因为N是常量,并且在声明时用字面值初始化,满足常量表达式的条件,即使它没有声明为constexpr

那么我什么时候需要使用constexpr呢?

  • 像上面的N这样的对象可以在没有声明constexpr的情况下用作常量表达式。对于以下所有满足条件的对象都是如此:
    • const
    • 具有整数或枚举类型
    • 在声明时使用一个本身就是常量表达式的表达式进行初始化。

[这是由于§5.19/2:常量表达式不能包含涉及“左值到右值修改”的子表达式,除非……是整数或枚举类型的glvalue。感谢Richard Smith纠正了我之前关于这适用于所有字面类型的说法。]

要使一个函数适用于常量表达式,它必须明确声明为`constexpr`;仅满足常量表达式函数的条件是不够的。例如:
```cpp template class list { };
constexpr int sqr1(int arg) { return arg * arg; }
int sqr2(int arg) { return arg * arg; }
int main() { const int X = 2; list mylist1; // 正确:sqr1是constexpr list mylist2; // 错误:sqr2不是constexpr } ```

何时可以/应该同时使用constconstexpr

A. 在对象声明中。当这两个关键字都指向要声明的同一对象时,这是不必要的。constexpr隐含了const

constexpr const int N = 5;

就是一样的

constexpr int N = 5;

然而,请注意,在某些情况下,关键词可能分别指代声明的不同部分。
static constexpr int N = 3;

int main()
{
  constexpr const int *NP = &N;
}

在这里,NP被声明为一个地址常量表达式,即一个指针本身就是一个常量表达式。(当地址是通过将地址运算符应用于静态/全局常量表达式生成时,这是可能的。)在这里,constexprconst都是必需的:constexpr始终引用被声明的表达式(这里是NP),而const引用int(它声明了一个指向常量的指针)。移除const将使表达式非法(因为(a)指向非常量对象的指针不能是常量表达式,(b)&N实际上是一个指向常量的指针)。 B. 在成员函数声明中。在C++11中,constexpr意味着const,而在C++14和C++17中不是这样。在C++11下声明的成员函数为
constexpr void f();

需要声明为

constexpr void f() const;

在C++14中,为了仍然能够作为一个“const”函数使用。

14
在我的看法中,“不一定在编译时计算”这个说法并没有“在编译时计算”的说法更有帮助。常量表达式的约束条件意味着编译器可以相对轻松地对其进行计算。如果这些约束条件未得到满足,编译器必须发出警告。由于没有副作用,你永远无法分辨编译器是否已经“计算”了它。 - aschepler
22
@aschepler 好的。我的主要观点是,如果你在非常量表达式上调用constexpr函数,比如普通变量,这是完全合法的,并且该函数将像任何其他函数一样使用。它不会在编译时进行求值(因为它不能)。也许你认为这很明显--但如果我声明一个constexpr函数将始终在编译时求值,这可能会被错误地理解。 - jogojapan
8
是的,我谈论的是constexpr对象,而不是函数。将constexpr用于对象时,我喜欢认为它会强制在编译时计算值;而将constexpr用于函数时,它允许根据需要在编译时或运行时计算函数。 - aschepler
5
更正一下:'const' 只是一个限制,你无法更改变量的值;它并不保证值不会被改变(例如,被其他人修改)。它是一个写属性,而不是读属性。 - Jared Grubb
4
这句话:“它保证成员函数不会修改任何非静态数据成员。”漏掉了一个重要细节。被标记为“mutable”的成员也可以被“const”成员函数修改。 - Omnifarious
显示剩余24条评论

173

const 应用于 变量,并且防止它们在代码中被修改

constexpr 声明此表达式的结果为编译时常量值,因此可以在数组长度、赋值给const变量等地方使用。Oli提供的链接中有很多优秀的示例。

基本上,它们是完全不同的2个概念,并且可以(并且应该)一起使用。


2
const和constexpr的用法,例如:http://en.cppreference.com/w/cpp/container/array/get - Manohar Reddy Poreddy
1
@ManoharReddyPoreddy 我认为https://en.cppreference.com/w/cpp/container/array/begin是一个更好的例子,它具有`constexpr T f(x) const签名,其中两者都适用于函数,而在constexpr const T f(x)(即array::get签名)中,const是返回类型的一部分,而不是函数属性(不确定标准术语的名称)。尽管这个答案没有承认在成员函数上使用const`。 - ted
@ted,我已经改用JavaScript编程语言有一段时间了,所以我几乎不记得之前发布的内容了:),因此无法对此发表评论。 - Manohar Reddy Poreddy
1
const并不能防止变量被修改,它只是防止程序员在没有明确表示想要这样做的情况下(通过强制类型转换)修改变量。 - einpoklum

94

概述

  • const 保证程序不会改变对象的值。然而,const 不保证对象经历哪种类型的初始化。

    考虑以下例子:

    const int mx = numeric_limits<int>::max();  // OK: runtime initialization
    

    max()函数仅返回一个字面值。然而,由于初始化程序是函数调用,mx会在运行时进行初始化。因此,您不能将其用作常量表达式:

    函数max()只是返回一个字面值。但是,由于初始化器是函数调用,mx会在运行时初始化。因此,您无法将其用作常数表达式:

    int arr[mx];  // error: “constant expression required”
    
  • constexpr是C++11中的一个新关键字,它可以替代宏和硬编码文字。在某些条件下,它还可以保证对象进行静态初始化。它控制表达式的评估时间。通过强制对其表达式进行编译时求值constexpr允许您定义真正的常量表达式,这对于时间关键型应用程序、系统编程、模板以及通常依赖于编译时常量的任何代码都是至关重要的。

  • 常量表达式函数

  • 一个常量表达式函数是一个声明为constexpr的函数。它的主体必须是非虚拟的并且只由一个返回语句组成(除了typedef和static assert)。它的参数和返回值必须具有文字类型。它可以与非常量表达式参数一起使用,但是这样做的结果不是一个常量表达式。

    常量表达式函数旨在替代硬编码文字,而不会牺牲性能或类型安全性。

    constexpr int max() { return INT_MAX; }           // OK
    constexpr long long_max() { return 2147483647; }  // OK
    constexpr bool get_val()
    {
        bool res = false;
        return res;
    }  // error: body is not just a return statement
    
    constexpr int square(int x)
    { return x * x; }  // OK: compile-time evaluation only if x is a constant expression
    const int res = square(5);  // OK: compile-time evaluation of square(5)
    int y = getval();
    int n = square(y);          // OK: runtime evaluation of square(y)
    

    常量表达式对象

    常量表达式对象 是使用 constexpr 声明的对象。它必须用常量表达式或由常量表达式构造函数用常量表达式参数构造的 rvalue 进行初始化。

    常量表达式对象的行为就像被声明为 const 一样,除了必须在使用前进行初始化,且其初始化程序必须是一个常量表达式。因此,常量表达式对象始终可以作为另一个常量表达式的一部分使用。

    struct S
    {
        constexpr int two();      // constant-expression function
    private:
        static constexpr int sz;  // constant-expression object
    };
    constexpr int S::sz = 256;
    enum DataPacket
    {
        Small = S::two(),  // error: S::two() called before it was defined
        Big = 1024
    };
    constexpr int S::two() { return sz*2; }
    constexpr S s;
    int arr[s.two()];  // OK: s.two() called after its definition
    

    常量表达式构造函数

    一个常量表达式构造函数是一个声明为constexpr的构造函数。它可以有成员初始化列表,但其函数体必须为空,除了typedefsstatic_asserts之外。它的参数必须具有字面类型。

    常量表达式构造函数允许编译器在编译时初始化对象,前提是构造函数的参数都是常量表达式。

    struct complex
    {
        // constant-expression constructor
        constexpr complex(double r, double i) : re(r), im(i) { }  // OK: empty body
        // constant-expression functions
        constexpr double real() { return re; }
        constexpr double imag() { return im; }
    private:
        double re;
        double im;
    };
    constexpr complex COMP(0.0, 1.0);         // creates a literal complex
    double x = 1.0;
    constexpr complex cx1(x, 0);              // error: x is not a constant expression
    const complex cx2(x, 1);                  // OK: runtime initialization
    constexpr double xx = COMP.real();        // OK: compile-time initialization
    constexpr double imaglval = COMP.imag();  // OK: compile-time initialization
    complex cx3(2, 4.6);                      // OK: runtime initialization
    

    Scott Meyers在他的书《Effective Modern C++》中给出了有关constexpr的提示:

    • constexpr对象是常量,并使用编译时已知的值进行初始化;
    • 当以编译时已知的值调用参数时,constexpr函数产生编译时结果;
    • constexpr对象和函数可以在比非constexpr对象和函数更广泛的上下文中使用;
    • constexpr是对象或函数界面的一部分。

    来源:Using constexpr to Improve Security, Performance and Encapsulation in C++


感谢提供这个出色的示例代码,展示了不同的情况。虽然其他解释也很好,但我发现看到代码实际运行更有用和易懂。它真正帮助巩固了我的理解。 - RTHarston
谢谢,这篇回答比被选中的那个更让我理解。 - LoneCodeRanger
numeric_limits<int>::max()是一个常量表达式。(因为它是constexpr) - apple apple

58

constconstexpr 都可以应用于变量和函数。尽管它们非常相似,但实际上它们是非常不同的概念。

constconstexpr 都意味着它们的值在初始化后不能被更改。例如:

const int x1=10;
constexpr int x2=10;

x1=20; // ERROR. Variable 'x1' can't be changed.
x2=20; // ERROR. Variable 'x2' can't be changed.

constconstexpr的主要区别在于它们初始化值(评估)的时间。虽然const变量的值可以在编译时和运行时评估,但constexpr始终在编译时评估。例如:

int temp=rand(); // temp is generated by the the random generator at runtime.

const int x1=10; // OK - known at compile time.
const int x2=temp; // OK - known only at runtime.
constexpr int x3=10; // OK - known at compile time.
constexpr int x4=temp; // ERROR. Compiler can't figure out the value of 'temp' variable at compile time so `constexpr` can't be applied here.

了解值是在编译时还是运行时已知的关键优势在于,可以在需要编译时常量的任何地方使用编译时常量。例如,C ++不允许您使用可变长度指定C数组。

int temp=rand(); // temp is generated by the the random generator at runtime.

int array1[10]; // OK.
int array2[temp]; // ERROR.

所以,这意味着:
const int size1=10; // OK - value known at compile time.
const int size2=temp; // OK - value known only at runtime.
constexpr int size3=10; // OK - value known at compile time.


int array3[size1]; // OK - size is known at compile time.
int array4[size2]; // ERROR - size is known only at runtime time.
int array5[size3]; // OK - size is known at compile time.

因此,const变量既可以定义编译时常量,例如size1,用于指定数组大小,也可以定义仅在运行时已知且不能用于定义数组大小的运行时常量,例如size2。另一方面,constexpr总是定义可以指定数组大小的编译时常量。

constconstexpr也可以应用于函数。一个const函数必须是一个成员函数(方法、运算符),其中应用const关键字意味着该方法无法更改其成员(非静态)字段的值。例如。

class test
{
   int x;

   void function1()
   {
      x=100; // OK.
   }

   void function2() const
   {
      x=100; // ERROR. The const methods can't change the values of object fields.
   }
};

constexpr是一个不同的概念。它将一个函数(成员或非成员)标记为可以在编译时评估的函数,如果编译时常量作为其参数传递。例如,您可以编写以下代码。

constexpr int func_constexpr(int X, int Y)
{
    return(X*Y);
}

int func(int X, int Y)
{
    return(X*Y);
}

int array1[func_constexpr(10,20)]; // OK - func_constexpr() can be evaluated at compile time.
int array2[func(10,20)]; // ERROR - func() is not a constexpr function.

int array3[func_constexpr(10,rand())]; // ERROR - even though func_constexpr() is the 'constexpr' function, the expression 'constexpr(10,rand())' can't be evaluated at compile time.

顺便说一下,constexpr函数是常规的C++函数,即使传递非常量参数也可以调用。但在这种情况下,您会得到非constexpr值。
int value1=func_constexpr(10,rand()); // OK. value1 is non-constexpr value that is evaluated in runtime.
constexpr int value2=func_constexpr(10,rand()); // ERROR. value2 is constexpr and the expression func_constexpr(10,rand()) can't be evaluated at compile time.

constexpr 可以应用于成员函数 (methods),操作符,甚至构造函数。例如:

class test2
{
    static constexpr int function(int value)
    {
        return(value+1);
    }

    void f()
    {
        int x[function(10)];


    }
};

一个更“疯狂”的示例。
class test3
{
    public:

    int value;

    // constexpr const method - can't chanage the values of object fields and can be evaluated at compile time.
    constexpr int getvalue() const
    {
        return(value);
    }

    constexpr test3(int Value)
        : value(Value)
    {
    }
};


constexpr test3 x(100); // OK. Constructor is constexpr.

int array[x.getvalue()]; // OK. x.getvalue() is constexpr and can be evaluated at compile time.

此外,在C语言中,存在constexpr int,但它的拼写为const int - curiousguy

45
根据Bjarne Stroustrup的著作《C++程序设计语言第四版》: • const: 大致意思是“我承诺不改变这个值”(§7.5)。这主要用于指定接口,以便数据可以传递给函数而不用担心它被修改。 编译器强制执行const所做的承诺。 • constexpr: 大致意思是“在编译时求值”(§10.4)。这主要用于指定常量,以允许例如:
const int dmv = 17; // dmv is a named constant
int var = 17; // var is not a constant
constexpr double max1 = 1.4*square(dmv); // OK if square(17) is a constant expression
constexpr double max2 = 1.4square(var); // error : var is not a constant expression
const double max3 = 1.4square(var); //OK, may be evaluated at run time
double sum(const vector<double>&); // sum will not modify its argument (§2.2.5)
vector<double> v {1.2, 3.4, 4.5}; // v is not a constant
const double s1 = sum(v); // OK: evaluated at run time
constexpr double s2 = sum(v); // error : sum(v) not constant expression

要使一个函数在编译器评估的表达式中可用,也就是在常量表达式中可用,它必须被定义为constexpr
例如:

constexpr double square(double x) { return x∗x; }


为了成为constexpr函数,一个函数必须相当简单:只有一个返回语句计算一个值。constexpr函数可以用于非常量参数,但是当这样做时,结果不是常量表达式。我们允许在不需要常量表达式的情况下以非常量表达式参数调用constexpr函数,这样我们就不需要基本上定义相同的函数两次:一次用于常量表达式,一次用于变量。
在一些地方,语言规则要求使用常量表达式(例如,数组边界(§2.2.5、§7.3)、case标签(§2.2.4、§9.4.2)、某些模板参数(§25.2)和使用constexpr声明的常量)。在其他情况下,编译时评估对性能非常重要。独立于性能问题之外,不可变性(具有不可改变状态的对象)的概念是一个重要的设计关注点(§10.4)。


仍然存在性能问题。似乎在运行时评估constexpr函数可能比非constexpr版本的函数慢。此外,如果我们有一个常量值,应该优先选择“const”还是“constexpr”?(更多是样式问题,生成的汇编代码看起来相同) - CoffeDeveloper

17

const int var 可以在运行时动态设置为一个值,一旦被设置为该值,就不能再改变。

constexpr int var 不能在运行时动态设置,而是在编译时设置。一旦被设置为该值,就不能再改变。

这里有一个实例:

int main(int argc, char*argv[]) {
    const int p = argc; 
    // p = 69; // cannot change p because it is a const
    // constexpr int q = argc; // cannot be, bcoz argc cannot be computed at compile time 
    constexpr int r = 2^3; // this works!
    // r = 42; // same as const too, it cannot be changed
}

以上代码片段可以正常编译,我已将导致错误的部分注释掉。

这里需要注意的关键概念是“编译时”和“运行时”的概念。C++引入了新的创新,旨在尽可能多地在编译时“知道”某些事情,以提高运行时的性能。

任何不涉及上述两个关键概念的解释都是妄想。


这是最好的答案。其他答案太啰嗦了。 - uzumaki

8
正如 @0x499602d2 已经指出的那样,const 只保证一个值在初始化后不能被改变,而 constexpr(在 C++11 中引入)则保证该变量是一个编译时常量。
考虑以下示例(来自 LearnCpp.com):
cout << "Enter your age: ";
int age;
cin >> age;

const int myAge{age};        // works
constexpr int someAge{age};  // error: age can only be resolved at runtime

7
我认为,任何一个答案都没有很清楚地说明它有哪些副作用或者说它是什么。
当在命名空间/文件范围内以字面量或表达式初始化时,constexprconst 是相同的;但对于函数,const 可以由任何函数初始化,但是非 constexpr 的函数(没有标记 constexpr 或非 constexpr 表达式)初始化的 constexpr 会生成编译器错误。对于变量来说,constexprconst 都是隐式的内部链接(实际上,在编译 -O1 以上时,它们不会存活到链接阶段;而 static 并不会强制编译器为 constconstexpr 发出一个内部(本地)链接符号,除非你取变量的地址。只有在使用 extern 表达时,constconstexpr 才会成为一个内部符号,例如:extern constexpr/const int i = 3;)。对于函数来说,constexpr 使函数永远无法到达链接阶段(无论定义中是否带有 externinline,以及是否使用 -O0 或 -Ofast),而 const 则永远不会,而 staticinline 只在 -O1 以上才具有这种效果。当使用 constexpr 函数初始化 const/constexpr 变量时,无论使用任何优化标志都会将负载优化掉,但如果函数只是 staticinline,或者变量不是 const/constexpr,则永远不会被优化。 标准编译(-O0)
#include<iostream>
constexpr int multiply (int x, int y)
{

  return x * y;
}

extern const int val = multiply(10,10);
int main () {
  std::cout << val;
} 

编译成

val:
        .long   100  //extra external definition supplied due to extern

main:
        push    rbp
        mov     rbp, rsp
        mov     esi, 100 //substituted in as an immediate
        mov     edi, OFFSET FLAT:_ZSt4cout
        call    std::basic_ostream<char, std::char_traits<char> >::operator<<(int)
        mov     eax, 0
        pop     rbp
        ret

__static_initialization_and_destruction_0(int, int):
        . 
        . 
        . 

然而

#include<iostream>
const int multiply (int x, int y)
{

  return x * y;
}

const int val = multiply(10,10); //constexpr is an error
int main () {
  std::cout << val;
}

编译为

multiply(int, int):
        push    rbp
        mov     rbp, rsp
        mov     DWORD PTR [rbp-4], edi
        mov     DWORD PTR [rbp-8], esi
        mov     eax, DWORD PTR [rbp-4]
        imul    eax, DWORD PTR [rbp-8]
        pop     rbp
        ret

main:
        push    rbp
        mov     rbp, rsp
        mov     eax, DWORD PTR val[rip]
        mov     esi, eax
        mov     edi, OFFSET FLAT:_ZSt4cout
        call    std::basic_ostream<char, std::char_traits<char> >::operator<<(int)
        mov     eax, 0
        pop     rbp
        ret

__static_initialization_and_destruction_0(int, int):
        . 
        . 
        . 
        mov     esi, 10
        mov     edi, 10
        call    multiply(int, int)
        mov     DWORD PTR val[rip], eax

显然,使用 constexpr 会导致 const/constexpr 文件作用域变量的初始化在编译时发生,并且不会产生全局符号;而不使用它会导致初始化在运行时的 main 函数之前发生。

使用 -Ofast 编译

即使使用 -Ofast 编译选项也无法优化掉这个加载操作!https://godbolt.org/z/r-mhif,因此您需要使用constexpr


constexpr函数也可以从其他constexpr函数中调用,以达到相同的结果。在函数上使用constexpr还可以防止在函数中进行不能在编译时完成的任何操作,例如对std::cout<<操作。

在块作用域中使用constexpr会产生相同的错误,如果由非constexpr函数初始化,则该值也会立即替换。

最终,constexpr的主要目的类似于C语言的内联函数,但仅在将函数用于初始化文件作用域变量时才有效(因为C语言不允许使用函数初始化文件作用域变量,但C++可以允许动态初始化文件作用域变量),即使使用extern/static,该函数也无法导出全局/局部符号给链接器,并且在C和C++中,块作用域变量分配函数可以仅使用-O1优化而不需要constexpr来进行内联。


链接器方面的观点很好。使用constexpr会导致更少的符号泄漏,因此一般来说它是否被认为更安全? - Goblinhack
1
@NeilMcGill 不完全是这样,因为使用 -O1 或更强的编译选项时,inline 和 static 会导致编译器不生成 multiply 的本地符号。Constexpr 是唯一一个优化掉 val 加载的关键字,但除此之外,它与在函数前面放置 static 或 inline 是相同的。我还忘了另外一件事。Constexpr 是唯一一个在 -O0 下不会发出函数符号的关键字,而 static 和 inline 则会。 - Lewis Kelsey

5

const和constexpr关键字概述

在C++中,如果一个const对象使用常量表达式初始化,那么我们可以在任何需要常量表达式的地方使用该const对象。

const int x = 10;
int a[x] = {0};

例如,在 switch 语句中,我们可以使用 case 语句。
constexpr 可以与数组一起使用。
constexpr 不是一种类型。
constexpr 关键字可以与 auto 关键字一起使用。
constexpr auto x = 10;

struct Data {   // We can make a bit field element of struct.   
    int a:x;
 };

如果我们使用常量表达式初始化一个const对象,那么由该const对象生成的表达式也是常量表达式。
常量表达式:其值可以在编译时计算出来的表达式。
例如:x*5-4 //这是一个常量表达式。对于编译器而言,输入该表达式和直接输入46没有区别。
初始化是强制性的。它只能用于读取操作,并且不能被更改。到目前为止,“const”和“constexpr”关键字之间没有区别。
注意:我们可以在同一声明中使用constexpr和const关键字。
constexpr const int* p;

Constexpr函数

通常情况下,函数的返回值在运行时获得。但是,当满足某些条件时,对constexpr函数的调用将在编译时作为常量获得。

注意: 如果在函数调用中发送给参数变量或者所有参数变量(如果有多个参数),则如果是C.E,则函数的返回值将在编译时计算。!!!

constexpr int square (int a){
return a*a;
}

constexpr int a = 3;
constexpr int b = 5;

int arr[square(a*b+20)] = {0}; //This expression is equal to int arr[35] = {0};

为了使一个函数成为constexpr函数,该函数的返回值类型和参数类型必须都是“字面类型”(literal type)。
constexpr函数是隐式的内联函数。
重要点:
所有的constexpr函数都不需要使用常量表达式进行调用。但如果这样做,计算会在编译时完成,否则会被视为普通函数调用。因此,在需要常量表达式的地方,我们将无法再使用此表达式。
下面是成为constexpr函数所需的条件:
1)函数的参数类型和返回值类型使用的类型必须是字面类型。
2)函数内部不能使用具有静态生命周期的局部变量。
3)如果函数正确无误,则当我们在编译时使用常量表达式调用此函数时,编译器会在编译时计算函数的返回值。
4)编译器需要看到函数的代码,因此constexpr函数几乎总是位于头文件中。
5)为了使我们创建的函数成为constexpr函数,该函数的定义必须位于头文件中。因此,包含该头文件的任何源文件都将看到函数定义。
额外奖励:
通常情况下,对于具有const和整数类型的静态数据成员,可以在类内进行默认成员初始化。然而,为了实现这一点,必须同时具备“const”和“整数类型”。
如果我们使用static constexpr,则不必是整数类型就可以在类内进行初始化。只要使用常量表达式进行初始化,就没有问题。
class Myclass  {
         const static int sx = 15;         // OK
         constexpr static int sy = 15;     // OK
         const static double sd = 1.5;     // ERROR
         constexpr static double sd = 1.5; // OK
 };

2
如果一个const对象被常量表达式初始化,我们可以在任何需要常量表达式的地方使用它。这仅适用于整数类型的常量。 - HolyBlackCat
这个注释正确吗?我得到的是 square(a*b+20) -> square(35) -> 1225... - Jay Elston

1

首先,两者都是C++中的限定符。

声明为const的变量必须被初始化,并且将来不能更改。因此,通常在编译之前就会有一个值作为const变量的值。

但是,对于constexpr,情况有所不同。

对于constexpr,您可以提供一个表达式,在程序编译期间可以进行评估。

显然,声明为constexpr的变量就像const一样不能在将来更改。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接