効果的な最新 C++ [実践] -> constexpr を使用できる場合は常に使用してください。

  • constexpr変数にはconstプロパティがあり、コンパイラが認識している値で初期化する必要があります。
  • constexpr関数が呼び出されたとき、渡された実際のパラメータ値がコンパイル時に既知であれば、返される結果もconstexpr変数になります。それ以外の場合は非constexpr変数を返します。
  • 関数または変数は、constexpr変数またはconstexpr関数よりも幅広いコンテキストで使用できます。constexprconstexpr
  • constexpr オブジェクトと関数のインターフェイスの一部です。

この条文を見る前に、次のコードを見てみましょう. これら 3 つはマクロ、const変更、およびconstexpr変更です. 何が違い、どのような意味を持っているのでしょうか? この記事では、それらを 1 つずつ説明します。

// 预处理
#define MEANING_OF_LIFE 42
// 常量:
const int MeaningOfLife = 42;
// constexpr-函数:
constexpr int MeaningOfLife () {
    
     return 42; }

マクロ --> マクロの使用を避ける

正直に言うと、マクロは言語の抽象的な機能の中で最も鈍感なツールであり、関数の皮をかぶった飢えた狼であり、飼いならすのが難しく、独自の方法であらゆる場所をさまよいますCC++マクロは避けてください。

マクロはテキスト置換機能として宣伝されており、その効果はc++構文規則や意味規則がまだ適用される前の前処理段階で発生します。

c++ではマクロはほとんど必要ありません関数呼び出しのオーバーヘッドを回避したり、関数と型のファミリーを指定したり、名前の競合を回避したりするために、よく理解されている定数を使用constまたは定義できます—ストラストルプenuminlinetemplatenamespace

マクロに関する第一のルールは、必要がない限りマクロを使用しないことです。ほぼすべてのマクロは、プログラミング言語、プログラム、またはプログラマーの欠陥を示しています。—ストラストルプ

マクロはスコープを無視し、型システムを無視し、他のすべての言語機能と規則を無視し、#defineファイルの残りの部分で定義されているシンボル ( ) をハイジャックします。マクロはシンボルや関数呼び出しのように見えますが、そうではありません。それは、使用される文脈に応じて、説得力のある驚くべきものに展開します。
マクロ内のエラーは、定義時ではなく、マクロの展開後にのみ報告される場合があります。
例外:

  1. ヘッダー (ファイル) にガードを追加する:意図しない複数のインクルードを防ぐために、すべてのヘッダー ファイルで一意の名前を持つインクルード ガード ( #include guard)を使用します
  2. NDEBUGアサーションは通常、デバッグ モード (マクロが定義されていない場合)でのみコードを生成するため、リリース ビルドには存在しません。
  3. #ifdefそして条件付きコンパイルでif defined条件付きコンパイル (システム依存部分など) では、コード内のあちこちに挿入を行わないようにしてください#ifdef代わりに、マクロを使用して共通のインターフェイスの複数の実装を駆動するようにコードを編成し、そのインターフェイスを一貫して使用します。

定数式

コンパイル時に評価できる式を定義します。

いわゆる定数式とは、≥1複数の定数 ( ) で構成される式を指します。つまり、式のメンバーがすべて定数である場合、その式は定数式です。これは、定数式が決定されると、その値を変更できないことも意味します。

int n = 1;
std::array<int, n> a1; // 错误:n 不是常量表达式
const int cn = 2;
std::array<int, cn> a2; // OK:cn 是常量表达式

C++プログラムの実行プロセスは通常、コンパイル、リンク、実行の 3 つの段階を経ます。なお、定数式と非定数式では計算のタイミングが異なり、非定数式はプログラムの実行段階でしか結果を計算できませんが、定数式の計算はプログラムのコンパイル段階で行われることが多く、式の計算はコンパイル段階で 1 回だけ行う必要があるため、プログラムの実行効率が大幅に向上し、プログラムを実行するたびに計算する時間が節約されます。

#include <array>
#include <iostream>
#include <array>
using namespace std;
void dis_1(const int x) {
    
    
    //error: 'x' is not a constant expression
    array<int, x> myarr{
    
    1, 2, 3, 4, 5};
    cout << myarr[1] << endl;
}
void dis_2() {
    
    
    const int x = 5;
    array<int, x> myarr{
    
    1, 2, 3, 4, 5};
    cout << myarr[1] << endl;
}
int main() {
    
    
    dis_1(5);
    dis_2();
}

const 何用

  1. 変数が変更されました。変数を変更できないことを示します。
  2. 変更されたポインタは、定数へのポインタとポインタ定数に分けられます。
  3. 定数参照は、関数による値のコピーや変更を避けるため、仮パラメータ型によく使用されます。
  4. メンバー関数が変更されました。メンバー関数内でメンバー変数を変更できないことを示します。
// 类
class A {
    
    
   private:
    const int a;  // 常对象成员,只能在初始化列表赋值

   public:
    // 构造函数
    A() : a(0){
    
    };
    A(int x) : a(x){
    
    };  // 初始化列表

    // const可用于对重载函数的区分
    int getValue();  // 普通成员函数
    int getValue() const;  // 常成员函数,不得修改类中的任何数据成员的值
};

void function() {
    
    
    // 对象
    A b;        // 普通对象,可以调用全部成员函数
    const A a;  // 常对象,只能调用常成员函数、更新常成员变量
    const A* p = &a;  // 常指针
    const A& q = a;   // 常引用

    // 指针
    char greeting[] = "Hello";
    char* p1 = greeting;              // 指针变量,指向字符数组变量
    const char* p2 = greeting;        // 指针变量,指向字符数组常量
    char* const p3 = greeting;        // 常指针,指向字符数组变量
    const char* const p4 = greeting;  // 常指针,指向字符数组常量
}

// 函数
void function1(const int Var);    // 传递过来的参数在函数内不可变
void function2(const char* Var);  // 参数指针所指内容为常量
void function3(char* const Var);  // 参数指针为常指针
void function4(const int& Var);   // 引用参数在函数内为常量

// 函数返回值
const int function5();  // 返回一个常数
const int*
function6();  // 返回一个指向常量的指针变量,使用:const int *p = function6();
int* const
function7();  // 返回一个指向变量的常指针,使用:int* const p = function7();

constは私たちの友達です: 定数値は理解しやすく、追跡し、分析しやすいため、可能な限り変数の代わりに定数を使用する必要があり、値を定義するときはデフォルトのオプションにする必要がありますconst。定数は安全であり、コンパイル時にチェックされます。 、そしてc++の型システムとシームレスに統合されています。不正な const で関数を呼び出す場合を除き、型をキャストしないでください。const

一般的な方法

const double gravity {
    
     9.8 };  // 首选在类型前使用const
int const sidesInSquare {
    
     4 }; // "east const" 风格, ok但是不推荐

const変数は初期化する必要があります

const変数は定義時に初期化する必要があり、その後、代入によって値を変更することはできません。

int main()
{
    
    
    const double gravity; // error: const variables must be initialized
    gravity = 9.9;        // error: const variables can not be changed

    return 0;
}

次のようにコンパイルします。

<source>: In function 'int main()':
<source>:7:18: error: uninitialized 'const gravity' [-fpermissive]
    7 |     const double gravity;  // error: const variables must be initialized
      |                  ^~~~~~~
<source>:8:13: error: assignment of read-only variable 'gravity'
    8 |     gravity = 9.9;         // error: const variables can not be changed

定数変数は他の変数 (非定数変数を含む) から初期化できることに注意してください。

#include <iostream>

int main() {
    
    
    int age{
    
    };
    age = 11;

    const int constAge{
    
    age};  // 使用非const值初始化const变量

    age = 5;  // ok: age 是非常数的,所以我们可以改变它的值
    // constAge = 6;  // error: constAge 是const,我们不能更改其值

    return 0;
}

渡し値と戻り値

  • 値渡しの場合は const を使用しないでください
  • 値で返す場合は const を使用しないでください

インライン関数

使用

  • これは、inline 関数が呼び出される場所に inline 関数の内容を書き込むことと同じです。
  • これは、関数に入るステップを実行せずに関数本体を直接実行することと同じです。
  • これはマクロと同等ですが、マクロよりも多くの型チェックがあり、実際には機能的な特徴があります。
  • ループ、再帰、スイッチなどの複雑な操作を含めることはできません。
  • クラス宣言で定義された関数は、仮想関数を除き、自動的かつ暗黙的にインライン関数とみなされます。
// 声明1(加 inline,建议使用)
inline int functionName(int first, int secend,...);
// 声明2(不加 inline)
int functionName(int first, int secend,...);
// 定义
inline int functionName(int first, int secend,...) {
    
    /****/};

// 类内定义,隐式内联
class A {
    
    
    int doA() {
    
     return 0; }         // 隐式内联
}
// 类外定义,需要显式内联
class A {
    
    
    int doA();
}
inline int A::doA() {
    
     return 0; }   // 需要显式内联

長所と短所

アドバンテージ

  1. インライン関数は、マクロ関数と同様に、呼び出された場所でコード展開を実行するため、パラメータのスタック、スタック フレームの開発とリサイクル、および結果の返却が不要になり、プログラムの実行速度が向上します
  2. マクロ関数と比較すると、インライン関数はコード展開時にセキュリティチェックや自動型変換(通常の関数と同じ)を行いますが、マクロ定義は行いません。
  3. クラス内で宣言および定義されたメンバー関数は自動的にインライン関数に変換されるため、インライン関数はクラスのメンバー変数にアクセスできますが、マクロ定義はアクセスできません。
  4. インライン関数は実行時にデバッグできますが、マクロ定義はデバッグできません。

欠点がある

  1. ** コードの肥大化。**インライン化にはコード拡張 (コピー) がかかり、関数呼び出しのオーバーヘッドが排除されます。関数本体内のコードの実行時間が関数呼び出しのオーバーヘッドに比べて比較的長い場合、効率の向上は小さくなります。一方、インライン関数呼び出しごとにコードをコピーする必要があるため、プログラムの合計コード サイズが増加し、より多くのメモリ領域を消費します。
  2. インライン関数はライブラリのアップグレードではアップグレードできません。直接リンクできる非インライン関数とは異なり、インライン関数を変更するには再コンパイルが必要です。
  3. インライン化するかどうかに関係なく、プログラマはそれを制御できませんinline 関数はコンパイラへの単なる提案であり、関数をインライン化するかどうかはコンパイラが決定します。

仮想関数はインライン化できますか?

  1. 仮想関数はインライン関数にすることができ、インライン化によって仮想関数を変更できますが、仮想関数が多態性を示す場合はインライン化できません
  2. インライン化とは、コンパイラがインライン化を推奨することを意味し、仮想関数のポリモーフィズムは実行時に行われ、コンパイラは実行時にどのコードを呼び出すべきかを知ることができないため、仮想関数がポリモーフィック (実行時) であるように見える場合は、仮想関数をインライン化できません。
  3. inline virtualインライン化できるのは次の場合のみです。コンパイラは、呼び出されるオブジェクトがどのクラス (例) であるかを知っていますBase::who()。これは、コンパイラがオブジェクトへのポインタや参照ではなく、実際のオブジェクトを持っている場合にのみ発生します。
#include <iostream>  
using namespace std;
class Base
{
    
    
public:
	inline virtual void who(){
    
    
		cout << "I am Base\n";
	}
	virtual ~Base() {
    
    }
};
class Derived : public Base{
    
    
public:
	inline void who() {
    
     // 不写inline时隐式内联
		cout << "I am Derived\n";
	}
};

int main(){
    
    
	// 此处的虚函数 who(),是通过类(Base)的具体对象(b)来调用的,编译期间就能确定了,所以它可以是内联的,但最终是否内联取决于编译器。 
	Base b;
	b.who();
	// 此处的虚函数是通过指针调用的,呈现多态性,需要在运行时期间才能确定,所以不能为内联。  
	Base *ptr = new Derived();
	ptr->who();
	// 因为Base有虚析构函数(virtual ~Base() {}),所以 delete 时,会先调用派生类(Derived)析构函数,再调用基类(Base)析构函数,防止内存泄漏。
	delete ptr;
	ptr = nullptr;
	system("pause");
	return 0;
}
コンセプト インライン関数
コンパイル時にコード/式を評価する際の関数呼び出しを削除します。 実行時に式を操作するため、関数呼び出しはほとんど削除されません。
変数または関数の値はコンパイル時に評価できます。 関数または変数の値はコンパイル時に評価できません。
外部リンクを意味するものではありません 外部連携という意味です。

constexpr- 一般化された定数式

constexpr機構

  • より一般的な定数式を提供します
  • ユーザー定義型を含む定数式を許可する
  • 初期化がコンパイル時に行われることを保証する方法を提供します

constexpr指定子は、関数または変数がコンパイル時に評価できることを宣言します。これらの変数と関数 (適切な関数引数を指定) は、コンパイル時の定数式が必要な場合に使用できます。

  • オブジェクトまたは非静的メンバー関数 (C++14前)を宣言するときに指定子を使用すると constexpr、両方が暗黙的に指定されます const
  • 関数または静的メンバー変数を宣言するC++17ときに指定子を使用するとconstexpr、同時にそれが暗黙的に行われます inline関数または関数テンプレートの宣言にconstexpr指定子がある場合、そのすべての宣言にその指定子が必要です。

例:

#include <array>
#include <iostream>

using namespace std;

constexpr int ximen = 12; //蕴含const
//若无constexpr则会报错 
//error: redeclaration 'int fun()' differs in 'constexpr' from previous declaration
constexpr int fun();
constexpr auto square(int x) -> int {
    
     return x * x; } //蕴含inline
constexpr auto twice_square(int x) -> int {
    
     return square(x); } //蕴含inline
constexpr int fun() {
    
     return 12;}  
int main() {
    
     return 0; }

コンパイルによって生成される内容は次のとおりです。

#include <array>
#include <iostream>

using namespace std;
constexpr const int ximen = 12;
inline constexpr int fun();

inline constexpr int square(int x)
{
    
    
  return x * x;
}
inline constexpr int twice_square(int x)
{
    
    
  return square(x);
}
//若无constexpr则会报错 
//error: redeclaration 'int fun()' differs in 'constexpr' from previous declaration
inline constexpr int fun()
{
    
    
  return 12;
}
int main()
{
    
    
  return 0;
}

通常の変数を変更する

constexpr 変数は次の要件を満たしている必要があります。

  • その型はリテラル型 ( LiteralType) である必要があります。
  • すぐに初期化する必要があります。
  • すべての暗黙的な変換、コンストラクター呼び出しなどを含むその初期化は、定数式である必要があります (これら 2 つの条件が満たされる場合、参照は次のように宣言できます: 参照されるオブジェクトは、初期化中に定数式によって初期化されます。 呼び出された暗黙的な変換はすべて定数式で行われます) constexpr。も定数式です)
  • 定数デストラクター( C++20 s)が必要です。つまり、次のようになります。
    • クラス型またはその (おそらく多次元) 配列ではない、または
    • これはクラス型またはその (おそらく多次元) 配列であり、クラス型にはデストラクターがあります。オブジェクトがその非サブオブジェクト (ただし、その子オブジェクトではない) に関連している場合、 constexprオブジェクトを破棄することのみを目的とする偽の式の場合) の有効期間は内で始まり、コアの定数式になります。emutablemutable e e

変数が翻訳単位ローカルでない場合constexpr 、定数式で使用できる翻訳単位ローカル エンティティを指すか参照するように、または指すか参照する (おそらく再帰的な) サブオブジェクトを持つように初期化されてはなりません。そのような存在に。このような初期化は、モジュール インターフェイス ユニット (プライベート モジュール セクションが存在する場合、その外側) またはモジュール分割では禁止されており、他のコンテキストではC++20 非推奨です
例:

#include <math.h>
#include <iostream>

using namespace std;

int main() {
    
    
    constexpr float x = 42.0;
    constexpr float y{
    
    108};
    constexpr float z = exp(5);
    constexpr int i;  // error: uninitialized 'const i' [-fpermissive]

    //可以使用constexpr运行时的值,但不能在编译时使用运行时变量。
    int j = 0;
    constexpr int k = j + 1; //  error: the value of 'j' is not usable in a constant expression
    int kk = x; // ok
    x = 100.00002; //error: assignment of read-only variable 'x'
}

修飾子関数

  • 関数は非void戻り型でなければなりません

  • 関数または関数テンプレートの宣言が次のようにconstexpr指定されている場合constexpr

constexpr int f1();  // OK, function declaration
int f1() {
    
               // Error, the constexpr specifier is missing
  return 55;
}     
  • constexprはコンパイル時の評価を保証するものではなく、プログラマが望む場合、またはコンパイラが最適化のためにそうすることを決定した場合に、定数式の引数がコンパイル時に評価できることを保証するだけです。constexpr定数式ではない引数を指定して関数またはコンストラクターが呼び出された場合、呼び出しはその関数constexprが r 関数ではないかのように動作し、結果の値は定数式ではありません。同様に、関数 return ステートメントの式が特定の callconstexprの定数式に評価されない場合、結果は定数式ではありません。
#include <iostream>

using namespace std;

constexpr int min(int x, int y) {
    
     return x < y ? x : y; }

void test(int v) {
    
    
    int m1 = min(-1, 2);            // 可能是编译时评估
    constexpr int m2 = min(-1, 2);  // 编译时评估
    int m3 = min(-1, v);            // 运行时评估
    // constexpr int m4 = min(-1, v);  //error: 'v' is not a constant expression
}

int main() {
    
    
    return 0;
}
        mov     DWORD PTR [rbp-4], -1
        mov     DWORD PTR [rbp-8], -1
        mov     eax, DWORD PTR [rbp-20]
        mov     esi, eax
        mov     edi, -1
        call    min(int, int)
        mov     DWORD PTR [rbp-12], eax
  • constexprnull つまり、関数は、定数式の引数が指定された場合にコンパイル時に評価できるような単純な形式である必要があり、関数本体は宣言、ステートメント、および単一のreturnステートメントのみで構成されます。パラメータ値は、パラメータ置換後にreturnステートメント内の式が定数式を生成するように存在する必要があります。
    階乗例
#include <iostream>
#include <vector>

using namespace std;

constexpr int fac(int n) {
    
    
    constexpr int max_exp = 2;
    if (0 <= n && n < max_exp) return 1; 
    int x = 1;
    for (int i = 2; i <= n; ++i) x *= i;
    return x;
}
int main() {
    
    
    int ddd = fac(5);
    cout << " ddd = " << ddd << endl;
}

アセンブリコード部分は次のとおりです。

main:
        push    rbp
        mov     rbp, rsp
        sub     rsp, 16
        mov     DWORD PTR [rbp-4], 120
//c++11的实例 constexpr 函数必须把一切放在单条 return 语句中
#include <iostream>
constexpr long long factorial(long long n) {
    
    
    return (n == 0) ? 1 : n * factorial(n - 1);
}
int main() {
    
    
    char test[factorial(3)];
    std::cout << factorial(7) << '\n';
}
//c++14的实例 无此要求
#include <iostream>
#include <utility>
constexpr long long factorial(int n) {
    
    
    long long result = 1;
    for (int i = 1; i <= n; ++i) {
    
    
        result *= i;
    }
    return result;
}
constexpr long long factorial1(long long n) {
    
    
    if (n == 0)
        return 1;
    else
        return n * factorial(n - 1);
}
int main() {
    
     std::cout << factorial(9) << "  "  <<factorial1(9) << std::endl; }

考慮する

	enum Flags {
    
     good=0, fail=1, bad=2, eof=4 };
	constexpr int operator|(Flags f1, Flags f2) {
    
     return Flags(int(f1)|int(f2)); }
	void f(Flags x)
	{
    
    
		switch (x) {
    
    
		case bad:         /* ... */ break;
		case eof:         /* ... */ break;
		case bad|eof:     /* ... */ break;
		default:          /* ... */ break;
		}
	}
  • コンパイル時に式を評価できるようにすることに加えて、コンパイル時に式を評価できるようにすることも必要です。前述の変数定義はconstexprこれを実行します (そして を暗黙的に示しますconst)。
	constexpr int x1 = bad|eof;	// ok

	void f(Flags f3)
	{
    
    
		constexpr int x2 = bad|f3;	// error: can't evaluate at compile time
		int x3 = bad|f3;		// ok
	}

多くの場合、グローバル オブジェクトまたは名前空間オブジェクト (通常は読み取り専用ストレージに置きたいオブジェクト) に対するコンパイル時の評価の保証が必要になります。

装飾されたコンストラクター

ユーザー定義型から定数式データ値を構築するには、constexpr宣言されたコンストラクターを使用できます。constexpr コンストラクターの関数本体には宣言とnull ステートメントのみを含めることができ、 constexpr変数を宣言したり、関数のような型を定義したりすることはできません。パラメータ置換後に定数式で初期化されるクラスのメンバーには、パラメータ値が存在する必要があります。これらの型のデストラクターは自明でなければなりません。

関数本体=delete;がそうでないconstexpr コンストラクターは、次の追加要件を満たす必要があります。

  • クラスまたは構造体のコンストラクターの場合、各サブオブジェクトと各非バリアント非静的データ メンバーを初期化する必要があります。クラスが共用体スタイルのクラスの場合、null 以外の匿名共用体メンバーごとに、バリアント メンバーを 1 つだけ初期化する必要があります (C++20 例)
  • 非 null 共用体のコンストラクターの場合、1 つの非静的データ メンバーだけが初期化されます (C++20 例)
  • 非静的メンバーと基本クラスを初期化するために選択されたすべてのコンストラクターは、constexpr コンストラクターである必要があります。
    constexprクラスのコンストラクターを変更する場合は、コンストラクターの関数本体が空である必要があり、初期化リストを使用して各メンバーに値を割り当てる場合は、定数式を使用する必要があります。
    として宣言されない限り=deleteconstexprコンストラクターの関数本体は通常は空であり、constexprすべてのデータ メンバーは初期化リストまたは他のコンストラクターを使用して初期化されます。
    コンストラクconstexprターは暗黙的にインライン化されます。
struct Point{
    
    
    constexpr Point(int _x, int _y)
        :x(_x),y(_y){
    
    }
    constexpr Point()
        :Point(0,0){
    
    }
    int x;
    int y;
};

constexpr Point pt = {
    
    10, 10};

コンストラクターを含む型の場合、その型のオブジェクトが関数からの値で返されるようにするには、constexpr通常、コピー コンストラクターもコンストラクターとして定義する必要があります。コピー コンストラクター、演算子のオーバーロードなど、クラスのメンバー関数は、関数の要件を満たす限り宣言できますこれにより、コンパイラはコンパイル時にオブジェクトをコピーしたり、オブジェクトに対して操作を実行したりすることができます。constexprconstexprconstexprconstexpr

コンストラクターの使用例はconstexpr次のとおりです。

  • 例1
#include <iostream>

using namespace std;

struct BASE {
    
    };

struct B2 {
    
    
    int i;
};

// NL is a non-literal type.
struct NL {
    
    
    virtual ~NL() {
    
    }
};

int i = 11;

struct D1 : public BASE {
    
    
    // OK, BASE的隐式默认构造函数是一个constexpr构造函数。
    constexpr D1() : BASE(), mem(55) {
    
    }

    // OK, BASE的隐式复制构造函数是一个constexpr构造函数。
    constexpr D1(const D1& d) : BASE(d), mem(55) {
    
    }

    // OK, 所有引用类型都是文字类型。
    constexpr D1(NL& n) : BASE(), mem(55) {
    
    }

    // 转换运算符不是constexpr.
    operator int() const {
    
     return 55; }

   private:
    int mem;
};

// 不能是虚继承
//  struct D2 : virtual BASE {
    
    
//    //error, D2 must not have virtual base class.
//    constexpr D2() : BASE(), mem(55) { }

// private:
//   int mem;
// };

struct D3 : B2 {
    
    
    // error, D3 必须不能包含try语句块
    //   constexpr D3(int) try : B2(), mem(55) { } catch(int) { }

    // error: 'constexpr' 构造函数内不能包含任何语句
    //   constexpr D3(char) : B2(), mem(55) { mem = 55; }

    // error, initializer for mem is not a constant expression.
    constexpr D3(double) : B2(), mem(i) {
    
    }

    // error, 隐式转换不是一个 constexpr.
    //   constexpr D3(const D1 &d) : B2(), mem(d) { }

    // error: invalid type for parameter 1 of 'constexpr' function 'constexpr D3::D3(NL)'
    //   constexpr D3(NL) : B2(), mem(55) { }

   private:
    int mem;
};

  • 例2
int main() {
    
    
    constexpr D3 d3instance(100);
    cout << " D3 i " << d3instance.i;

    return 0;
}
	struct Point {
    
    
		int x,y;
		constexpr Point(int xx, int yy) : x(xx), y(yy) {
    
     }
	};
	constexpr Point origo(0,0);
	constexpr int z = origo.x;
	constexpr Point a[] = {
    
    Point(0,0), Point(1,1), Point(2,2) };
	constexpr int x = a[1].x;	// x becomes 1
constexpr int f1();  // OK, function declaration
int f1() {
    
               // Error, the constexpr specifier is missing
  return 55;
}     

テンプレート機能の修正

C++11構文では、constexpr テンプレート関数は変更できますが、テンプレート内の型が不確実であるため、テンプレート関数のインスタンス化された関数が定数式関数の要件を満たしているかどうかも不確かです
例 1:

#include <iostream>

using namespace std;

template <typename Type>
constexpr Type max111(Type a, Type b) {
    
    
    return a < b ? b : a;
}

int main() {
    
    
    int maxv = max111(11, 5);
    cout << maxv << endl;
    string maxv1 = max111("12", "22");
    cout << maxv1 << endl;
    return 0;
}

アセンブリコードは次のとおりです。
ここに画像の説明を挿入

例 2:
コンパイル時は同じですが、実行時には 2 つの異なる関数呼び出しが行われます。

#include <iostream>

using namespace std;

template <int i>
auto f() {
    
    
    if constexpr (i == 0)
        return 10;
    else
        return std::string("hello");
}

int main() {
    
    
    cout << f<0>() << endl;  // 10
    cout << f<1>() << endl;  // hello
}

コンパイルされた出力は次のとおりです。

#include <iostream>

using namespace std;

template<int i>
auto f()
{
    
    
  if constexpr(i == 0) {
    
    
    return 10;
  } else /* constexpr */ {
    
    
    return std::basic_string<char, std::char_traits<char>, std::allocator<char> >(std::basic_string<char, std::char_traits<char>, std::allocator<char> >("hello", std::allocator<char>()));
  } 
  
}
#ifdef INSIGHTS_USE_TEMPLATE
template<>
int f<0>()
{
    
    
  if constexpr(true) {
    
    
    return 10;
  } 
  
}
#endif
#ifdef INSIGHTS_USE_TEMPLATE
template<>
std::basic_string<char, std::char_traits<char>, std::allocator<char> > f<1>()
{
    
    
  if constexpr(false) {
    
    
  } else /* constexpr */ {
    
    
    return std::basic_string<char, std::char_traits<char>, std::allocator<char> >(std::basic_string<char, std::char_traits<char>, std::allocator<char> >("hello", std::allocator<char>()));
  } 
  
}
#endif
int main()
{
    
    
  std::cout.operator<<(f<0>()).operator<<(std::endl);
  std::operator<<(std::cout, f<1>()).operator<<(std::endl);
  return 0;
}

constexprラムダ(C++17)

説明する

constexpr関数呼び出し演算子または特定の演算子テンプレートの特殊化が関数であることを明示的に指定しますconstexpr この指定子が存在しない場合は、関数呼び出し演算子または指定された演算子テンプレートの特殊化がたまたますべての関数要件を満たしている場合にconstexpr なります constexpr

using F = ret(*)(params);
constexpr operator F() const noexcept;

template<template-params> using fptr_t = /*see below*/;
template<template-params>
constexpr operator fptr_t<template-params>() const noexcept;

lambdaこのユーザー定義の変換関数は、式のキャプチャ リストが空の場合にのみ定義されますpublicこれは( C++17 以降)クロージャ オブジェクトのconstexpr非仮想的かつ非明示的なconst noexcept メンバー関数です。

使用

  1. lambda 式は、定数式でキャプチャまたは導入される各データ メンバーの初期化がconstexpr許可されている場合、定数式として宣言することも、定数式で使用することもできます。
#include <iostream>
#include <numeric>

using namespace std;

constexpr int Increment(int n) {
    
    
    return [n] {
    
     return n + 1; }();
}
int main() {
    
    
    int y = 32;
    auto answer = [&y]() {
    
    
        int x = 10;
        return y + x;
    };
    constexpr int incr = Increment(100);
    cout << "Increment = " << incr; //101
    return 0;
}
  1. lambdaの結果が関数の要件を満たしている場合、constexpr それは暗黙的です constexpr
#include <iostream>
#include <vector>
using namespace std;
int main() {
    
    
    std::vector<int> c = {
    
    1, 2, 3, 4, 5, 6, 7};
    auto answer = [](int n) {
    
     return 32 + n; };
    constexpr int response = answer(10);
    cout << " response = " << response << endl; // response = 42
}

アセンブリコードは次のとおりです。

        mov     DWORD PTR [rbp-80], 1
        mov     DWORD PTR [rbp-76], 2
        mov     DWORD PTR [rbp-72], 3
        mov     DWORD PTR [rbp-68], 4
        mov     DWORD PTR [rbp-64], 5
        mov     DWORD PTR [rbp-60], 6
        mov     DWORD PTR [rbp-56], 7
        lea     rax, [rbp-80]
        mov     r12, rax
        mov     r13d, 7
        lea     rax, [rbp-37]
        mov     rdi, rax
        call    std::allocator<int>::allocator() [complete object constructor]
        lea     rdx, [rbp-37]
        mov     rsi, r12
        mov     rdi, r13
        mov     rcx, r12
        mov     rbx, r13
        mov     rdi, rbx
        lea     rax, [rbp-112]
        mov     rcx, rdx
        mov     rdx, rdi
        mov     rdi, rax
        call    std::vector<int, std::allocator<int> >::vector(std::initializer_list<int>, std::allocator<int> const&) [complete object constructor]
        lea     rax, [rbp-37]
        mov     rdi, rax
        call    std::allocator<int>::~allocator() [complete object destructor]
        mov     DWORD PTR [rbp-36], 42
  1. lambda暗黙的または明示的constexprに関数ポインターに変換した場合、結果の関数も次のようになりますconstexpr
#include <iostream>
#include <vector>

using namespace std;
auto Increment = [](int n) {
    
     return n + 1; };

constexpr int (*inc)(int) = Increment;

int main() {
    
    
    constexpr int response = inc(22);
    cout << " response = " << response << endl;
}

アセンブリコードは次のとおりです。

Increment::{
    
    lambda(int)#1}::operator()(int) const:
        push    rbp
        mov     rbp, rsp
        mov     QWORD PTR [rbp-8], rdi
        mov     DWORD PTR [rbp-12], esi
        mov     eax, DWORD PTR [rbp-12]
        add     eax, 1
        pop     rbp
        ret
Increment::{
    
    lambda(int)#1}::_FUN(int):
        push    rbp
        mov     rbp, rsp
        sub     rsp, 16
        mov     DWORD PTR [rbp-4], edi
        mov     eax, DWORD PTR [rbp-4]
        mov     esi, eax
        mov     edi, 0
        call    Increment::{
    
    lambda(int)#1}::operator()(int) const
        leave
        ret
Increment:
        .zero   1
.LC0:
        .string " response = "
main:
        push    rbp
        mov     rbp, rsp
        sub     rsp, 16
        mov     DWORD PTR [rbp-4], 23
        mov     esi, OFFSET FLAT:.LC0
        mov     edi, OFFSET FLAT:_ZSt4cout
        call    std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)
        mov     esi, 23
        mov     rdi, rax
        call    std::basic_ostream<char, std::char_traits<char> >::operator<<(int)
        mov     esi, OFFSET FLAT:_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_
        mov     rdi, rax
        call    std::basic_ostream<char, std::char_traits<char> >::operator<<(std::basic_ostream<char, std::char_traits<char> >& (*)(std::basic_ostream<char, std::char_traits<char> >&))
        mov     eax, 0
        leave
        ret

C++11 での Constexpr の例

const int array_size1 (int x) {
    
    
  return x+1;
}
// Error, constant expression required in array declaration
int array[array_size1(10)];    

constexpr int array_size2 (int x) {
    
     
  return x+1; 
} 
// OK, constexpr functions can be evaluated at compile time
// and used in contexts that require constant expressions. 
int array[array_size2(10)];     

struct S {
    
    
  S() {
    
     }
  constexpr S(int) {
    
     }
  constexpr virtual int f() {
    
      // Error, f must not be virtual.
    return 55;
  }       
};

struct NL {
    
    
  ~NL() {
    
     }  // The user-provided destructor (even if it is trivial) 
             // makes the type a non-literal type.
};

constexpr NL f1() {
    
      // Error, return type of f1 must be a literal type.
  return NL();
}       

constexpr int f2(NL) {
    
      // Error, the parameter type NL is not a literal type.
  return 55; 
}                 

constexpr S f3() {
    
    
  return S();
}

enum {
    
     val = f3() };  // Error, initialization of the return value in f3()
                      // uses a non-constexpr constructor.

constexpr void f4(int x) {
    
      // Error, return type should not be void.
  return;
}

constexpr int f5(int x) {
    
     // Error, function body contains more than
  if (x<0)                // return statement.
    x = -x;
  return x;
}

「C++」には常に定数式の概念があります。これらの式 ( 3 + 4 など) は、コンパイル時と実行時に常に同じ結果を生成します。定数式はコンパイラにとって最適化の機会であり、多くの場合、コンパイラはコンパイル時に定数式を実行し、結果をプログラムにハードコードします。また、場所によっては、C++ 仕様では定数式の使用が必要です。配列を定義するには定数式が必要であり、列挙値は定数式である必要があります。

ただし、定数式に関数呼び出しやオブジェクト コンストラクターを含めることはできません。次のような単純なコードは無効です。

int get_five() {
    
    return 5;}
int some_value[get_five() + 7]; // Create an array of 12 integers. Ill-formed C++

これは定数式C++03ではないため、無効です。コンパイラには、それが実行時に定数であるかどうかを知る方法がありません。理論的には、この関数はグローバル変数に影響を与えたり、他の非実行時定数関数を呼び出したりすることができます。キーワードが導入されました。これにより、ユーザーは関数またはオブジェクトのコンストラクターがコンパイル時の定数であることを保証できます。上記の例は次のように書き換えることができます。get_five() + 7C++03get_five()
C++11 constexpr

constexpr int get_five() {
    
    return 5;}
int some_value[get_five() + 7]; // Create an array of 12 integers. Valid C++11

これにより、コンパイラはget_five() それがコンパイル時の定数であることを理解し、検証できるようになります。

constexprconst 違いがある

  1. 目的が違います。
  • constexpr主に最適化に使用されますが、const 実際には値constなどのオブジェクトに使用されますPi どちらもメンバー メソッドに適用できます。constメソッドが誤って変更されないようにメンバー メソッドを作成します。
  • constexprアイデアは使用することですコンパイル時に式を評価するコードを実行する時間を節約します
    ここに画像の説明を挿入
  • const 非静的メンバー関数でのみ使用できますが、constexprパラメーターと戻り値の型がリテラル型である必要がある場合は、メンバー関数と非メンバー関数、さらにはコンストラクターでも使用できます。
  1. 機能が異なります: 以下の一般的な代替品でconstexprはありませんconst(逆も同様):
  • const の主な機能は、 (オブジェクトが他のインターフェイスを通じて変更される可能性がある場合でも) インターフェイスを通じてオブジェクトを変更しないという考えを表現することです。オブジェクト定数を宣言すると、コンパイラーに優れた最適化の機会が提供されます。特に、オブジェクトが として宣言され、constそのアドレスが取得されない場合、コンパイラーは通常、コンパイル時にその初期化子を計算し (ただし、これは保証されていません)、生成されたコードにオブジェクトを送信する代わりに、そのオブジェクトをテーブル内に保持することができます。 。
  • constexprの主な機能は、コンパイル時に計算できる範囲を拡張し、そのような計算をタイプセーフにすることですconstexpr宣言されたオブジェクトの初期値はコンパイル時に計算されます。これらは基本的にコンパイラ テーブルに保持され、必要な場合にのみ生成されたコードに送信される値です。

オブジェクトに適用される場合の基本的な違いは次のとおりです。

  • constオブジェクトを定数として宣言します。これは、オブジェクトの値が初期化されると変更されないことが保証され、コンパイラーはこの事実を最適化に利用できることを意味します。また、プログラマが初期化後に変更すべきではないオブジェクトを変更するコードを作成することを防ぐのにも役立ちます。
  • constexpr標準ステートメントで定数式と呼ばれるものに適したオブジェクトを宣言します。ただし、constexprこれが唯一の方法ではないことに注意してください。
  • const変数と変数constexprの主な違いは、const 変数の初期化を実行時まで延期できることです。constexpr変数はコンパイル時に初期化する必要がありますすべてのconstexpr変数は ですconst

関数に適用すると、基本的な違いは次のとおりです。

  • const非静的メンバー関数にのみ使用でき、一般関数には使用できません。これにより、メンバー関数が非静的データ メンバーを変更しないことが保証されます (変更可能な変更可能なデータ メンバーを除く)。
  • constexprメンバー関数、非メンバー関数、コンストラクターの両方に使用できます。これは、関数が定数式での使用に適していることを宣言します。コンパイラは、特定の条件を満たす場合にのみ関数を受け入れます。最も重要なことは次のとおりです。
    • 関数本体は非仮想かつ非常に単純である必要があります。typedefsとを除きstatic assert、 return ステートメントは 1 つだけ許可されます。コンストラクターの場合、初期化リストtypedefsと静的アサーションのみが許可されます。(= default= delete許可されます。)
    • ではルールがより緩和され、宣言ステートメント、および 、以外のラベルが付いたステートメント、非リテラル型の変数の定義、初期化を実行しない静的またはスレッドの保存期間変数の定義が、 からの関数で許可されます。c++14次に変数の定義です。constexpr:asmgotocasedefaulttry-block
    • パラメーターと戻り値の型はリテラル型 (つまり、一般的に言えば、非常に単純な型、通常はスカラーまたは集合体) である必要があります。

const任意の関数で初期化できますが、 constexpr非(式または非式でマークされてい constexprない関数) で初期化するとコンパイラ エラーが生成されます。 constexprconstexpr

const int function5(){
    
     return 100;} // 返回一个常数
constexpr int function8(){
    
    return 100;}
int main(){
    
    
    constexpr int temp5 = function5();// error: call to non-'constexpr' function 'const int function5()'
    constexpr int temp6 = funtion8(); //ok
    const int temp7 = function8();    //ok
    return 0;
}
#include <iostream>

template <typename T>
void f(T t) {
    
    
    static_assert(t == 1, "");
}
constexpr int one = 1;
int main() {
    
    
    f(one);
    return 0;
}

gccコンパイルされた出力は次のとおりです。

<source>: In instantiation of 'void f(T) [with T = int]':
<source>:10:6:   required from here
<source>:5:21: error: non-constant condition for static assertion
    5 |     static_assert(t == 1, "");
      |                   ~~^~~~
<source>:5:21: error: 't' is not a constant expression

fの本体内のt は定数式ではないため、 のstatic _ assert オペランドとして使用できません (コンパイル時のアサーション チェックには定数式が必要ですstatic_assert) constexprboolその理由は、コンパイラがこのような関数を生成できないためです。

template <typename T>
void f1(constexpr T t) {
    
    
  static_assert(t == 1, "");
}
int main() {
    
    
		constexpr int one = 1;
    f1(one);
    return 0;
}

gcc コンパイル エラー出力は次のとおりです。

<source>:4:9: error: a parameter cannot be declared 'constexpr'
    4 | void f1(constexpr T t) {
    
    
      |         ^~~~~~~~~
<source>: In function 'int main()':
<source>:9:8: error: 'one' was not declared in this scope
    9 |     f1(one);
      |        ^~~

実際のパラメータが定数式ではないstatic_assertという事実は、定数式を必要とするオブジェクトやその他のオブジェクト内で、それを型以外のテンプレート パラメータとして、配列バインディング パラメータとして使用できないことを意味します。
パラメーターの受け渡しを通じて特異性を維持するには、値を型としてエンコードconstexprし、その型のオブジェクト (必ずしもオブジェクトである必要はありません) を関数に渡す必要があります。関数はテンプレートである必要があり、その型でエンコードされた値にアクセスできますconstexprconstexprconstexpr

constexpr int sqrt(int i) {
    
    
  if (i < 0) throw "i should be non-negative";
  return 111;
}
constexpr int two = sqrt(4); // ok: did not attempt to throw
constexpr int error = sqrt(-4); // error: can't throw in a constant expression

gccコンパイルされた出力は次のとおりです。

<source>:14:27:   in 'constexpr' expansion of 'sqrt(-4)'
<source>:10:14: error: expression '<throw-expression>' is not a constant expression
   10 |   if (i < 0) throw "i should be non-negative";
#include <iostream>

template <typename T>
constexpr int f(T& n, bool touch_n) {
    
    
    if (touch_n) n + 1;
    return 1;
}
int main() {
    
    
    int n = 0;
    constexpr int i = f(n, false);  // ok
    constexpr int j = f(n, true);   // error
    return 0;
}

gcc標準でコンパイルされた出力はc++11次のとおりです。

<source>: In function 'int main()':
<source>:11:24: error: 'constexpr int f(T&, bool) [with T = int]' called in a constant expression
   11 |     constexpr int i = f(n, false);  // ok
      |                       ~^~~~~~~~~~
<source>:4:15: note: 'constexpr int f(T&, bool) [with T = int]' is not usable as a 'constexpr' function because:
    4 | constexpr int f(T& n, bool touch_n) {
    
    
      |               ^
<source>:12:24: error: 'constexpr int f(T&, bool) [with T = int]' called in a constant expression
   12 |     constexpr int j = f(n, true);   // error
      |                       ~^~~~~~~~~
#include <iostream>
template <typename T>
constexpr int f(T n) {
    
    
    return 1;
}
int main() {
    
    
    int n = 0;
    constexpr int i = f(n);
    return 0;
}

最初のシナリオとの唯一の違いは、パラメーターが参照ではなく値によって受け入れられるようになったことですfしかし、これは大きな違いを生みます。実際には、コンパイラにコピーを作成しn、このコピーを に渡すように依頼しますfただし、nがないconstexprため、その値は実行時にのみわかります。コンパイラは、実行時にのみ値がわかる変数を (コンパイル時に) どのようにコピーするのでしょうか? もちろん、そうではありません。

<source>: In function 'int main()':
<source>:10:26: error: the value of 'n' is not usable in a constant expression
   10 |     constexpr int i = f(n);
      |                          ^
<source>:9:9: note: 'int n' is not const
    9 |     int n = 0;
      |      

追記

  • すべての関数宣言ビットを作成しようとしないでくださいconstexprほとんどの計算は実行時に行うのが最適です。
  • 高度なランタイム構成やビジネス ロジックに依存する可能性のあるものはAPI使用しないでください constexprコンパイラはこのカスタマイズを評価できないため、それに依存する関数はリファクタリングするかAPI 削除する必要があります constexpr constexpr
  • 定数が期待される非関数を呼び出すと、 constexprコンパイラはエラーになります。

参考

[1]定数式
[2] constexpr と const の違いは何ですか?
[3]付録 I: 高度な constexpr
[4] C++ で const と constexpr を使用する場合
[5] constexpr 指定子 (C++11 以降)
[6] CppCoreガイドライン F.4
[7] constexpr-cpp.md

おすすめ

転載: blog.csdn.net/MMTS_yang/article/details/126344277