ความแตกต่างระหว่าง "constexpr" และ "const"


593

ความแตกต่างระหว่างconstexprและconstคืออะไร

  • เมื่อใดที่ฉันสามารถใช้หนึ่งในนั้นได้
  • เมื่อใดที่ฉันจะใช้ทั้งสองและฉันจะเลือกได้อย่างไร

71
constexprสร้างค่าคงที่เวลารวบรวม constเพียงหมายความว่าค่านั้นไม่สามารถเปลี่ยนแปลงได้
0x499602D2


อาจเป็นบทความจากboost/hanaห้องสมุดนี้สามารถรู้แจ้งconstexprปัญหาบางอย่างที่คุณสามารถใช้constexprและไม่สามารถ: boost.org/doc/libs/1_69_0/libs/hana/doc/html/ …
Andry

@ 0x499602D2 " ก็หมายความว่าไม่สามารถเปลี่ยนค่าได้ " สำหรับสเกลาร์ที่เริ่มต้นด้วยตัวอักษรค่าที่ไม่สามารถเปลี่ยนแปลงได้ก็คือค่าคงที่เวลาในการคอมไพล์
curiousguy

@curtguy ใช่ความคิดเห็นของฉันเป็นอย่างมาก oversimplified เป็นที่ยอมรับว่าฉันยังใหม่ไม่ได้constexprเช่นกัน :)
0x499602D2

คำตอบ:


587

ความหมายและไวยากรณ์เบื้องต้น

คำหลักทั้งสองสามารถใช้ในการประกาศของวัตถุเช่นเดียวกับฟังก์ชั่น ความแตกต่างพื้นฐานเมื่อนำไปใช้กับวัตถุคือ:

  • constประกาศเป็นวัตถุอย่างต่อเนื่อง สิ่งนี้แสดงถึงการรับประกันว่าเมื่อเริ่มต้นแล้วค่าของวัตถุนั้นจะไม่เปลี่ยนแปลงและคอมไพเลอร์สามารถใช้ประโยชน์จากข้อเท็จจริงนี้เพื่อการปรับให้เหมาะสม นอกจากนี้ยังช่วยป้องกันโปรแกรมเมอร์จากการเขียนรหัสที่ปรับเปลี่ยนวัตถุที่ไม่ได้ตั้งใจจะแก้ไขหลังจากการเริ่มต้น

  • constexprประกาศเป็นวัตถุที่เหมาะสำหรับการใช้งานในสิ่งที่เรียกว่ามาตรฐานการแสดงออกอย่างต่อเนื่อง แต่โปรดทราบว่าconstexprไม่ใช่วิธีเดียวที่จะทำเช่นนี้

เมื่อนำไปใช้กับฟังก์ชั่นความแตกต่างพื้นฐานคือ:

  • constสามารถใช้สำหรับฟังก์ชั่นสมาชิกที่ไม่คงที่เท่านั้นไม่ใช่ฟังก์ชั่นทั่วไป มันให้การรับประกันว่าฟังก์ชั่นสมาชิกไม่ได้แก้ไขใด ๆ ของสมาชิกข้อมูลไม่คงที่

  • constexprสามารถใช้กับฟังก์ชั่นสมาชิกและไม่ใช่สมาชิกเช่นเดียวกับการก่อสร้าง มันบอกพอดีฟังก์ชั่นสำหรับการใช้งานในการแสดงออกอย่างต่อเนื่อง คอมไพเลอร์จะยอมรับก็ต่อเมื่อฟังก์ชั่นตรงตามเกณฑ์ที่กำหนด (7.1.5 / 3,4) ที่สำคัญที่สุดคือ(†) :

    • ฟังก์ชันของฟังก์ชันต้องไม่ใช่แบบเสมือนจริงและเรียบง่ายมาก: นอกเหนือจาก typedefs และ static asserts แล้วreturnอนุญาตให้ใช้คำสั่งเดียวเท่านั้น ในกรณีของ Constructor อนุญาตให้มีรายการเริ่มต้นเท่านั้น typedefs และ static assert เท่านั้น ( = defaultและ= deleteได้รับอนุญาตเช่นกัน)
    • ตั้งแต่ C ++ 14 กฎมีความผ่อนคลายมากขึ้นสิ่งที่ได้รับอนุญาตตั้งแต่นั้นมาในฟังก์ชั่น constexpr: asmการประกาศgotoคำสั่งคำสั่งที่มีป้ายชื่อนอกเหนือจากcaseและdefaultลองบล็อกคำจำกัดความของตัวแปรประเภทที่ไม่ใช่ตัวอักษร คำจำกัดความของตัวแปรของระยะเวลาเก็บข้อมูลแบบคงที่หรือเธรดคำจำกัดความของตัวแปรที่ไม่มีการเริ่มต้น
    • อาร์กิวเมนต์และชนิดส่งคืนจะต้องเป็นตัวอักษร (เช่นโดยทั่วไปการพูดประเภทที่ง่ายมากโดยทั่วไปคือสเกลาร์หรือมวลรวม)

การแสดงออกอย่างต่อเนื่อง

ดังที่ได้กล่าวไว้ข้างต้นconstexprประกาศวัตถุทั้งสองรวมทั้งฟังก์ชั่นที่เหมาะสำหรับการใช้งานในการแสดงออกคงที่ นิพจน์คงที่เป็นมากกว่าค่าคงที่:

  • สามารถใช้ในสถานที่ที่ต้องการการประเมินเวลาคอมไพล์ตัวอย่างเช่นพารามิเตอร์เทมเพลตและตัวระบุขนาดอาร์เรย์:

    template<int N>
    class fixed_size_list
    { /*...*/ };
    
    fixed_size_list<X> mylist;  // X must be an integer constant expression
    
    int numbers[X];  // X must be an integer constant expression
  • แต่หมายเหตุ:

    • การประกาศสิ่งที่constexprไม่จำเป็นต้องรับประกันว่าจะมีการประเมิน ณ เวลารวบรวม มันสามารถใช้สำหรับเช่นนี้ แต่มันสามารถใช้ในสถานที่อื่น ๆ ที่มีการประเมินในเวลาทำงานเช่นกัน

    • วัตถุที่อาจจะเหมาะสำหรับการใช้งานในการแสดงออกอย่างต่อเนื่องโดยไม่ต้องconstexprถูกประกาศ ตัวอย่าง:

      int main()
      {
        const int N = 3;
        int numbers[N] = {1, 2, 3};  // N is constant expression
      }

    สิ่งนี้เป็นไปได้เพราะNการคงที่และกำหนดค่าเริ่มต้น ณ เวลาที่ประกาศด้วยตัวอักษรเป็นไปตามเกณฑ์สำหรับนิพจน์คงที่แม้ว่าจะไม่ได้ประกาศconstexprก็ตาม

ดังนั้นเมื่อไม่จริงผมต้องใช้constexpr?

  • วัตถุเหมือนNข้างต้นสามารถนำมาใช้เป็นแสดงออกอย่างต่อเนื่องโดยไม่ต้องconstexprถูกประกาศ สิ่งนี้เป็นจริงสำหรับวัตถุทั้งหมดที่:

    • const
    • ชนิดหนึ่งหรือการแจงนับและ
    • เริ่มต้น ณ เวลาที่ประกาศพร้อมกับนิพจน์ที่เป็นนิพจน์คงที่

    [นี่คือเนื่องจาก§5.19 / 2: นิพจน์คงที่จะต้องไม่รวม subexpressions ที่เกี่ยวข้องกับ "การปรับเปลี่ยน lvalue-to-rvalue เว้นแต่ […] glvalue ของอินทิกรัลหรือการแจงนับประเภท [... ]" ขอบคุณ Richard Smith สำหรับการแก้ไขของฉัน ก่อนหน้านี้อ้างว่าสิ่งนี้เป็นจริงสำหรับตัวอักษรทุกประเภท]

  • เพื่อให้ฟังก์ชั่นเหมาะสำหรับการใช้งานในการแสดงออกคงที่มันจะต้องมีการประกาศอย่างชัดเจนconstexpr; มันไม่เพียงพอสำหรับมันเพียงเพื่อตอบสนองเกณฑ์สำหรับฟังก์ชั่นการแสดงออกอย่างต่อเนื่อง ตัวอย่าง:

    template<int N>
    class list
    { };
    
    constexpr int sqr1(int arg)
    { return arg * arg; }
    
    int sqr2(int arg)
    { return arg * arg; }
    
    int main()
    {
      const int X = 2;
      list<sqr1(X)> mylist1;  // OK: sqr1 is constexpr
      list<sqr2(X)> mylist2;  // wrong: sqr2 is not constexpr
    }

เมื่อใดที่ฉัน / ฉันควรใช้ทั้งสองconstและconstexpr ร่วมกัน?

A. ในการประกาศวัตถุ สิ่งนี้ไม่จำเป็นเมื่อคำหลักทั้งสองอ้างถึงวัตถุเดียวกันที่จะประกาศ หมายถึงconstexprconst

constexpr const int N = 5;

เป็นเช่นเดียวกับ

constexpr int N = 5;

อย่างไรก็ตามโปรดทราบว่าอาจมีสถานการณ์ที่คำหลักแต่ละคำอ้างถึงส่วนต่าง ๆ ของการประกาศ:

static constexpr int N = 3;

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

ที่นี่NPมีการประกาศเป็นค่าคงที่ของนิพจน์เช่นตัวชี้ที่เป็นค่าคงที่ของนิพจน์ (นี่เป็นไปได้เมื่อที่อยู่ถูกสร้างขึ้นโดยการใช้ตัวดำเนินการที่อยู่กับการแสดงออกคงที่ / ทั่วโลกคงที่) ที่นี่ทั้งสองconstexprและconstจำเป็นต้องใช้: constexprหมายถึงการแสดงออกที่ประกาศ (ที่นี่NP) เสมอในขณะที่constอ้างถึงint(มันประกาศตัวชี้ เพื่อ const) การลบconstจะทำให้การแสดงออกที่ผิดกฎหมาย (เพราะ (a) ตัวชี้ไปยังวัตถุที่ไม่ใช่ const ไม่สามารถแสดงออกอย่างต่อเนื่องและ (b) &Nในความเป็นจริงตัวชี้ไปยังคงที่)

B. ในการประกาศฟังก์ชั่นสมาชิก ใน C ++ 11 constexprหมายถึงconstในขณะที่อยู่ใน C ++ 14 และ C ++ 17 ที่ไม่ใช่ตัวพิมพ์เล็ก ฟังก์ชั่นสมาชิกประกาศภายใต้ C ++ 11 เป็น

constexpr void f();

จะต้องมีการประกาศให้เป็น

constexpr void f() const;

ภายใต้ C ++ 14 เพื่อให้ยังคงใช้งานได้เป็นconstฟังก์ชั่น


3
IMO "ไม่จำเป็นต้องประเมินเวลารวบรวม" มีประโยชน์น้อยกว่าคิดว่าเป็น "ประเมินเวลารวบรวม" ข้อ จำกัด ในการแสดงออกอย่างต่อเนื่องหมายความว่ามันจะค่อนข้างง่ายสำหรับคอมไพเลอร์ในการประเมินมัน คอมไพเลอร์ต้องบ่นหากข้อ จำกัด เหล่านั้นไม่เป็นที่พอใจ เนื่องจากไม่มีผลข้างเคียงคุณจึงไม่สามารถบอกความแตกต่างได้ว่าคอมไพเลอร์ "ประเมิน" หรือไม่
aschepler

10
@aschepler แน่นอน ประเด็นหลักของฉันคือถ้าคุณเรียกใช้constexprฟังก์ชั่นในการแสดงออกที่ไม่คงที่เช่นตัวแปรสามัญสิ่งนี้จะถูกกฎหมายอย่างสมบูรณ์และฟังก์ชั่นจะถูกใช้เหมือนฟังก์ชั่นอื่น ๆ จะไม่ถูกประเมินในเวลารวบรวม (เพราะไม่สามารถทำได้) บางทีคุณอาจคิดว่ามันชัดเจน - แต่ถ้าฉันบอกว่าฟังก์ชั่นที่ประกาศว่าconstexprจะได้รับการประเมินในเวลารวบรวมมันอาจตีความได้ในทางที่ผิด
jogojapan

5
ใช่ฉันกำลังพูดถึงconstexprวัตถุไม่ใช่ฟังก์ชั่น ฉันชอบที่จะคิดconstexprเกี่ยวกับวัตถุในการบังคับใช้การประเมินเวลารวบรวมของค่าและconstexprในฟังก์ชั่นที่ช่วยให้การประเมินฟังก์ชั่นในเวลารวบรวมหรือเวลาทำงานตามความเหมาะสม
aschepler

2
การแก้ไข: 'const' เป็นเพียงข้อ จำกัด ที่คุณไม่สามารถเปลี่ยนค่าของตัวแปรได้ มันไม่ได้ให้สัญญาใด ๆ ว่าค่าจะไม่เปลี่ยนแปลง (เช่นโดยคนอื่น) มันเป็นคุณสมบัติการเขียนไม่ใช่คุณสมบัติการอ่าน
Jared Grubb

3
ประโยคนี้มันให้การรับประกันว่าฟังก์ชั่นสมาชิกจะไม่แก้ไขสมาชิกข้อมูลที่ไม่คงที่ คิดถึงรายละเอียดที่สำคัญอย่างหนึ่ง สมาชิกที่ทำเครื่องหมายว่าmutableอาจถูกแก้ไขโดยconstฟังก์ชั่นสมาชิก
Omnifarious

119

constใช้กับตัวแปรและป้องกันไม่ให้มีการแก้ไขในรหัสของคุณ

constexprบอกคอมไพเลอร์ว่าการแสดงออกนี้ส่งผลให้เกิดค่าคงที่เวลารวบรวมดังนั้นจึงสามารถใช้ในสถานที่เช่นความยาวของอาเรย์การกำหนดconstตัวแปร ฯลฯลิงก์ที่ให้โดย Oli มีตัวอย่างที่ยอดเยี่ยมมากมาย

โดยพื้นฐานแล้วพวกเขาเป็นแนวคิดที่แตกต่างกัน 2 ประการและสามารถ (และควร) ใช้ร่วมกัน


2
การใช้งาน const & constexpr เช่น en.cppreference.com/w/cpp/container/array/get
Manohar Reddy Poreddy

64

ภาพรวม

  • constรับประกันว่าโปรแกรมไม่ได้เปลี่ยนค่าของวัตถุ อย่างไรก็ตามconstไม่รับประกันว่าชนิดของการเริ่มต้นวัตถุผ่าน

    พิจารณา:

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

    ฟังก์ชันmax()ส่งคืนค่าตามตัวอักษรเท่านั้น แต่เนื่องจากการเริ่มต้นคือการเรียกฟังก์ชั่นmxผ่านการรันไทม์การเริ่มต้น ดังนั้นคุณไม่สามารถใช้มันเป็นนิพจน์คงที่ :

    int arr[mx];  // error: “constant expression required”
  • constexprเป็นคำหลัก C ++ 11 ใหม่ที่ทำให้คุณไม่จำเป็นต้องสร้างมาโครและตัวอักษรแบบฮาร์ดโค้ด นอกจากนี้ยังมีการค้ำประกันภายใต้เงื่อนไขบางประการว่าวัตถุได้รับการเริ่มต้นคงที่ มันควบคุมเวลาการประเมินผลของการแสดงออก ด้วยการบังคับใช้การประเมินเวลาคอมไพล์ของนิพจน์นั้นconstexprให้คุณกำหนดนิพจน์ค่าคงที่จริงที่มีความสำคัญสำหรับแอปพลิเคชันที่มีความสำคัญต่อเวลาการเขียนโปรแกรมระบบแม่แบบและการพูดโดยทั่วไปในรหัสใด ๆ

ฟังก์ชั่นการแสดงออกอย่างต่อเนื่อง

ฟังก์ชั่นอย่างต่อเนื่องแสดงออกconstexprเป็นฟังก์ชั่นการประกาศ เนื้อความของมันต้องไม่ใช่แบบเสมือนจริงและประกอบด้วยคำสั่ง return เดียวเท่านั้นนอกเหนือจาก typedefs และ asserts แบบคงที่ อาร์กิวเมนต์และค่าส่งคืนต้องมีชนิดตามตัวอักษร มันสามารถใช้กับอาร์กิวเมนต์ที่ไม่คงที่ - นิพจน์ แต่เมื่อทำเสร็จแล้วผลที่ได้จะไม่แสดงออกอย่างต่อเนื่อง

ฟังก์ชั่นการแสดงออกอย่างต่อเนื่องหมายถึงการแทนที่มาโครและตัวอักษรแบบฮาร์ดโค้ดโดยไม่ลดทอนประสิทธิภาพหรือความปลอดภัยของประเภท

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ยกเว้นว่ามันต้องมีการเริ่มต้นก่อนการใช้งานและ initializer จะต้องมีการแสดงออกอย่างต่อเนื่อง ดังนั้นวัตถุนิพจน์คงที่สามารถใช้เป็นส่วนหนึ่งของการแสดงออกคงที่อื่นเสมอ

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เป็นตัวสร้างประกาศ มันสามารถมีรายการเริ่มต้นของสมาชิก แต่ร่างกายของมันจะต้องว่างเปล่านอกเหนือจาก typedefs และการยืนยันแบบคงที่ อาร์กิวเมนต์ต้องมีประเภทตามตัวอักษร

คอนสตรัคชั่นนิพจน์คงที่อนุญาตให้คอมไพเลอร์เริ่มต้นวัตถุในเวลาคอมไพล์โดยมีเงื่อนไขว่าอาร์กิวเมนต์ของคอนสตรัคเตอร์เป็นนิพจน์คงที่ทั้งหมด

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

เคล็ดลับจากหนังสือEffective Modern C ++โดย Scott Meyers เกี่ยวกับconstexpr:

  • constexpr วัตถุเป็น const และเริ่มต้นด้วยค่าที่รู้จักในระหว่างการรวบรวม;
  • constexpr ฟังก์ชั่นสร้างผลลัพธ์เวลารวบรวมเมื่อมีการเรียกด้วยข้อโต้แย้งที่มีค่าเป็นที่รู้จักในระหว่างการรวบรวม;
  • constexprวัตถุและฟังก์ชั่นอาจถูกใช้ในบริบทที่กว้างกว่าไม่ใช่constexprวัตถุและฟังก์ชั่น;
  • constexpr เป็นส่วนหนึ่งของอินเทอร์เฟซของวัตถุหรือฟังก์ชัน

ที่มา: การใช้ constexpr การปรับปรุงการรักษาความปลอดภัยประสิทธิภาพและการ Encapsulation ใน C


ขอบคุณสำหรับโค้ดตัวอย่างที่ดีที่แสดงสถานการณ์ที่แตกต่างกัน ยิ่งไปกว่าคำอธิบายอื่น ๆ ฉันพบว่าการเห็นโค้ดในการทำงานมีประโยชน์และเข้าใจได้ง่ายกว่ามาก มันช่วยเสริมสร้างความเข้าใจของฉันในสิ่งที่เกิดขึ้น
RTHarston

35

จากหนังสือ "The C ++ Programming Language 4th Editon" โดย Bjarne Stroustrup
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 xx; }


ในการเป็น constexpr ฟังก์ชั่นจะต้องค่อนข้างง่าย: เพียงแค่ return-statement การคำนวณค่า ฟังก์ชัน constexpr สามารถใช้สำหรับอาร์กิวเมนต์ที่ไม่คงที่ แต่เมื่อทำเสร็จแล้วผลลัพธ์จะไม่ใช่นิพจน์คงที่ เราอนุญาตให้ฟังก์ชัน constexpr ถูกเรียกด้วยอาร์กิวเมนต์ไม่คงที่ในนิพจน์ในบริบทที่ไม่ต้องการนิพจน์คงที่ดังนั้นเราจึงไม่ hav e กำหนดฟังก์ชันหลักเดียวกันสองครั้ง: หนึ่งครั้งสำหรับนิพจน์คงที่และอีกครั้งสำหรับตัวแปร
ในบางแห่งนิพจน์คงที่จำเป็นต้องใช้ตามกฎภาษา (เช่นขอบเขตของอาร์เรย์ (§2.2.5, §7.3), เลเบลเคส (§2.2.4, §9.4.2), อาร์กิวเมนต์ของเทมเพลตบางส่วน (§25.2) และ ค่าคงที่ประกาศโดยใช้ constexpr) ในกรณีอื่นการประเมินเวลาคอมไพล์เป็นสิ่งสำคัญสำหรับประสิทธิภาพ อิสระจากปัญหาด้านประสิทธิภาพแนวคิดเกี่ยวกับความไม่สามารถเปลี่ยนได้ (ของวัตถุที่มีสถานะไม่แน่นอน) เป็นประเด็นการออกแบบที่สำคัญ (§10.4)


ยังคงมีปัญหาด้านประสิทธิภาพ ดูเหมือนว่าฟังก์ชั่น constexpr ถ้าประเมินที่รันไทม์อาจช้ากว่าฟังก์ชั่นรุ่นที่ไม่ใช่ constexpr นอกจากนี้หากเรามีค่าคงที่เราควรเลือก "const" หรือ "constexpr" (ยิ่งมีคำถามเกี่ยวกับรูปแบบที่สร้างขึ้นจะมีลักษณะเหมือนกัน)
CoffeDeveloper

31

ทั้งconstและconstexprสามารถนำไปใช้กับตัวแปรและฟังก์ชั่น แม้ว่าพวกเขาจะมีความคล้ายคลึงกันในความเป็นจริงพวกเขามีแนวคิดที่แตกต่างกันมาก

ทั้งconstและconstexprหมายความว่าค่าของพวกเขาไม่สามารถเปลี่ยนแปลงได้หลังจากการเริ่มต้น ตัวอย่างเช่น:

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.

ความแตกต่างหลักระหว่างconstและconstexprคือเวลาที่ทราบค่าเริ่มต้น (ประเมิน) ในขณะที่ค่าของ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-arrays ด้วยความยาวของตัวแปร

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จะกำหนดค่าคงที่เวลาการคอมไพล์ที่สามารถระบุขนาดอาร์เรย์ได้เสมอ

ทั้งconstและconstexprสามารถนำไปใช้กับฟังก์ชั่นได้เช่นกัน 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.
   }
};

A 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 ++ ปกติที่สามารถเรียกว่าแม้ว่าข้อโต้แย้งที่ไม่คงที่จะถูกส่งผ่าน แต่ในกรณีนั้นคุณจะได้รับค่าที่ไม่ใช่ค่าเริ่มต้น

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สามารถนำไปใช้ยังรวมถึงฟังก์ชั่นสมาชิก (วิธีการ) ผู้ประกอบการและแม้กระทั่งการก่อสร้าง ตัวอย่างเช่น

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

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

5

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
}

ตัวอย่างข้างต้นรวบรวมข้อมูลได้ดีและฉันได้แสดงความคิดเห็นกับสิ่งที่ทำให้เกิดข้อผิดพลาด

ความคิดที่สำคัญที่นี่เพื่อรับทราบเป็นความคิดของและcompile time run timeนวัตกรรมใหม่ได้รับการแนะนำใน C ++ โดยมีจุดประสงค์เพื่อให้ได้มากที่สุดเท่าที่จะเป็นไปได้** know **ในการรวบรวมเพื่อปรับปรุงประสิทธิภาพในขณะทำงาน


3

ฉันไม่คิดว่าคำตอบใด ๆ ที่ทำให้ชัดเจนว่าสิ่งที่มีผลข้างเคียงหรือจริงๆมันคืออะไร

constexprและconstที่ namespace / file-scope เหมือนกันเมื่อเริ่มต้นด้วยตัวอักษรหรือการแสดงออก; แต่ด้วยฟังก์ชั่นconstสามารถเริ่มต้นได้โดยฟังก์ชั่นใด ๆ แต่constexprเริ่มต้นโดยไม่ใช่ constexpr (ฟังก์ชั่นที่ไม่ได้ทำเครื่องหมายด้วย constexpr หรือนิพจน์ที่ไม่ใช่ constexpr) จะสร้างข้อผิดพลาดคอมไพเลอร์ ทั้งสองconstexprและconstมีการเชื่อมโยงภายในโดยนัยสำหรับตัวแปร (ที่จริงแล้วพวกมันไม่รอดที่จะไปสู่ขั้นตอนการเชื่อมโยงหากรวบรวม -O1 และแข็งแกร่งขึ้นและstaticไม่บังคับให้คอมไพเลอร์เปล่งสัญลักษณ์ linker ภายใน (ภายใน) สำหรับconstหรือconstexprเมื่อ - O1 หรือแข็งแกร่งกว่าเพียงครั้งเดียวก็คือถ้าคุณใช้ที่อยู่ของตัวแปรconstและconstexprจะเป็นสัญลักษณ์ภายในเว้นแต่จะแสดงด้วยexternเช่นextern constexpr/const int i = 3;จำเป็นต้องใช้) ในฟังก์ชั่นconstexprทำให้ฟังก์ชั่นอย่างถาวรไม่เคยไปถึงขั้นตอนการเชื่อมโยง (โดยไม่คำนึงถึงexternหรือinlineในคำนิยามหรือ -O0 หรือ -Ofast) ในขณะที่constไม่เคยทำstaticและinlineมีเพียงผลกระทบนี้เมื่อ -O1 ขึ้นไป เมื่อconst/ constexprตัวแปร initialised โดยconstexprฟังก์ชั่นโหลดที่ดีที่สุดเสมอออกมาพร้อมกับการเพิ่มประสิทธิภาพธงใด ๆ แต่มันก็ไม่เคยที่ดีที่สุดว่าฟังก์ชั่นเป็นเพียงstaticหรือinlineหรือถ้าตัวแปรไม่ได้เป็น/constconstexpr

การรวบรวมมาตรฐาน (-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

แม้ - Fast ไม่เพิ่มประสิทธิภาพการโหลด! https://godbolt.org/z/r-mhifดังนั้นคุณต้องการ constexpr


constexprสามารถเรียกconstexprใช้ฟังก์ชันจากภายในฟังก์ชันอื่นเพื่อผลลัพธ์เดียวกัน constexprในฟังก์ชั่นนี้ยังป้องกันการใช้สิ่งที่ไม่สามารถทำได้ในเวลารวบรวมในฟังก์ชั่น; ตัวอย่างเช่นการเรียกผู้ประกอบการใน<<std::cout

constexprที่ขอบเขตของบล็อกทำงานแบบเดียวกันกับที่มันสร้างข้อผิดพลาดหากเริ่มต้นด้วยฟังก์ชั่นที่ไม่ใช่ constexpr; ค่าจะถูกแทนที่ด้วยในทันที

ในตอนท้ายวัตถุประสงค์หลักของมันก็เหมือนกับฟังก์ชั่นอินไลน์ของ C แต่จะมีผลเฉพาะเมื่อฟังก์ชันใช้ในการเริ่มต้นตัวแปรขอบเขตไฟล์ (ซึ่งฟังก์ชันไม่สามารถทำงานบน C ได้ แต่สามารถใช้กับ C ++ ได้ ตัวแปรขอบเขต) ยกเว้นฟังก์ชันไม่สามารถส่งออกสัญลักษณ์โกลบอล / โลคัลไปยังตัวเชื่อมโยงได้เช่นกันแม้จะใช้extern/staticซึ่งคุณสามารถทำได้inlineบน C; ฟังก์ชั่นการกำหนดตัวแปรขอบเขตบล็อกสามารถ inline ได้อย่างง่ายดายโดยใช้การเพิ่มประสิทธิภาพ -O1 โดยไม่ต้องconstexprใช้ C และ C ++


จุดดีที่ลิงเกอร์ โดยทั่วไปแล้วจะถือว่าปลอดภัยกว่าหรือไม่ในการใช้ constexpr เนื่องจากมีการรั่วของสัญลักษณ์น้อยกว่า
Neil McGill

1
@NeilMcGill ไม่ได้เพราะอินไลน์และสแตติกจะทำให้คอมไพเลอร์ไม่ปล่อยสัญลักษณ์โลคอลสำหรับการคูณหากคอมไพล์โดยใช้ -O1 หรือแรงกว่า Constexpr เป็นเพียงตัวเดียวที่ปรับโหลดให้เหมาะสม แต่นอกเหนือจากนั้นมันเหมือนกับการใส่แบบสแตติกหรืออินไลน์ก่อนฟังก์ชั่น ฉันก็ลืมอย่างอื่นเช่นกัน Constexpr เป็นคำหลักเดียวที่ไม่ปล่อยสัญลักษณ์สำหรับฟังก์ชันใน -O0, static และ inline do
Lewis Kelsey

1

ก่อนอื่นทั้งคู่เป็นตัวระบุใน c ++ ตัวแปรที่ประกาศจะต้องเริ่มต้นและไม่สามารถเปลี่ยนแปลงได้ในอนาคต ดังนั้นโดยทั่วไปแล้วตัวแปรที่ประกาศเป็น const จะมีค่าแม้กระทั่งก่อนการรวบรวม

แต่สำหรับ constexpr มันแตกต่างกันเล็กน้อย

สำหรับ constexpr คุณสามารถให้นิพจน์ที่สามารถประเมินได้ระหว่างการคอมไพล์ของโปรแกรม

เห็นได้ชัดว่าตัวแปรที่ประกาศเป็น constexper ไม่สามารถเปลี่ยนแปลงได้ในอนาคตเช่นเดียวกับ const

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.