可能な限り constexpr を使用してください
constexpr
変数にはconst
プロパティがあり、コンパイラが認識している値で初期化する必要があります。constexpr
関数が呼び出されたとき、渡された実際のパラメータ値がコンパイル時に既知であれば、返される結果もconstexpr
変数になります。それ以外の場合は非constexpr
変数を返します。- 関数または変数は、非
constexpr
変数またはconstexpr
関数よりも幅広いコンテキストで使用できます。constexpr
constexpr
constexpr
オブジェクトと関数のインターフェイスの一部です。
この条文を見る前に、次のコードを見てみましょう. これら 3 つはマクロ、const
変更、およびconstexpr
変更です. 何が違い、どのような意味を持っているのでしょうか? この記事では、それらを 1 つずつ説明します。
// 预处理
#define MEANING_OF_LIFE 42
// 常量:
const int MeaningOfLife = 42;
// constexpr-函数:
constexpr int MeaningOfLife () {
return 42; }
マクロ --> マクロの使用を避ける
正直に言うと、マクロは言語の抽象的な機能の中で最も鈍感なツールであり、関数の皮をかぶった飢えた狼であり、飼いならすのが難しく、独自の方法であらゆる場所をさまよいます
C
。C++
マクロは避けてください。
マクロはテキスト置換機能として宣伝されており、その効果は
c++
構文規則や意味規則がまだ適用される前の前処理段階で発生します。
c++
ではマクロはほとんど必要ありません。関数呼び出しのオーバーヘッドを回避したり、関数と型のファミリーを指定したり、名前の競合を回避したりするために、よく理解されている定数を使用const
または定義できます。—ストラストルプenum
inline
template
namespace
マクロに関する第一のルールは、必要がない限りマクロを使用しないことです。ほぼすべてのマクロは、プログラミング言語、プログラム、またはプログラマーの欠陥を示しています。—ストラストルプ
マクロはスコープを無視し、型システムを無視し、他のすべての言語機能と規則を無視し、#define
ファイルの残りの部分で定義されているシンボル ( ) をハイジャックします。マクロはシンボルや関数呼び出しのように見えますが、そうではありません。それは、使用される文脈に応じて、説得力のある驚くべきものに展開します。
マクロ内のエラーは、定義時ではなく、マクロの展開後にのみ報告される場合があります。
例外:
- ヘッダー (ファイル) にガードを追加する:意図しない複数のインクルードを防ぐために、すべてのヘッダー ファイルで一意の名前を持つインクルード ガード (
#include guard
)を使用します。 NDEBUG
アサーションは通常、デバッグ モード (マクロが定義されていない場合)でのみコードを生成するため、リリース ビルドには存在しません。#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
何用
- 変数が変更されました。変数を変更できないことを示します。
- 変更されたポインタは、定数へのポインタとポインタ定数に分けられます。
- 定数参照は、関数による値のコピーや変更を避けるため、仮パラメータ型によく使用されます。
- メンバー関数が変更されました。メンバー関数内でメンバー変数を変更できないことを示します。
// 类
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; } // 需要显式内联
長所と短所
アドバンテージ
- インライン関数は、マクロ関数と同様に、呼び出された場所でコード展開を実行するため、パラメータのスタック、スタック フレームの開発とリサイクル、および結果の返却が不要になり、プログラムの実行速度が向上します。
- マクロ関数と比較すると、インライン関数はコード展開時にセキュリティチェックや自動型変換(通常の関数と同じ)を行いますが、マクロ定義は行いません。
- クラス内で宣言および定義されたメンバー関数は自動的にインライン関数に変換されるため、インライン関数はクラスのメンバー変数にアクセスできますが、マクロ定義はアクセスできません。
- インライン関数は実行時にデバッグできますが、マクロ定義はデバッグできません。
欠点がある
- ** コードの肥大化。**インライン化にはコード拡張 (コピー) がかかり、関数呼び出しのオーバーヘッドが排除されます。関数本体内のコードの実行時間が関数呼び出しのオーバーヘッドに比べて比較的長い場合、効率の向上は小さくなります。一方、インライン関数呼び出しごとにコードをコピーする必要があるため、プログラムの合計コード サイズが増加し、より多くのメモリ領域を消費します。
- インライン関数はライブラリのアップグレードではアップグレードできません。直接リンクできる非インライン関数とは異なり、インライン関数を変更するには再コンパイルが必要です。
- インライン化するかどうかに関係なく、プログラマはそれを制御できません。inline 関数はコンパイラへの単なる提案であり、関数をインライン化するかどうかはコンパイラが決定します。
仮想関数はインライン化できますか?
- 仮想関数はインライン関数にすることができ、インライン化によって仮想関数を変更できますが、仮想関数が多態性を示す場合はインライン化できません。
- インライン化とは、コンパイラがインライン化を推奨することを意味し、仮想関数のポリモーフィズムは実行時に行われ、コンパイラは実行時にどのコードを呼び出すべきかを知ることができないため、仮想関数がポリモーフィック (実行時) であるように見える場合は、仮想関数をインライン化できません。
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
オブジェクトを破棄することのみを目的とする偽の式の場合) の有効期間は内で始まり、コアの定数式になります。e
mutable
mutable
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
constexpr
null
つまり、関数は、定数式の引数が指定された場合にコンパイル時に評価できるような単純な形式である必要があり、関数本体は宣言、ステートメント、および単一の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
クラスのコンストラクターを変更する場合は、コンストラクターの関数本体が空である必要があり、初期化リストを使用して各メンバーに値を割り当てる場合は、定数式を使用する必要があります。
として宣言されない限り=delete
、constexpr
コンストラクターの関数本体は通常は空であり、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
通常、コピー コンストラクターもコンストラクターとして定義する必要があります。コピー コンストラクター、演算子のオーバーロードなど、クラスのメンバー関数は、関数の要件を満たす限り宣言できます。これにより、コンパイラはコンパイル時にオブジェクトをコピーしたり、オブジェクトに対して操作を実行したりすることができます。constexpr
constexpr
constexpr
constexpr
コンストラクターの使用例は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
メンバー関数です。
使用
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;
}
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
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() + 7
C++03
get_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()
それがコンパイル時の定数であることを理解し、検証できるようになります。
constexpr
const
違いがある
- 目的が違います。
constexpr
主に最適化に使用されますが、const
実際には値const
などのオブジェクトに使用されますPi
。どちらもメンバー メソッドに適用できます。const
メソッドが誤って変更されないようにメンバー メソッドを作成します。constexpr
アイデアは使用することですコンパイル時に式を評価するコードを実行する時間を節約します。
const
非静的メンバー関数でのみ使用できますが、constexpr
パラメーターと戻り値の型がリテラル型である必要がある場合は、メンバー関数と非メンバー関数、さらにはコンストラクターでも使用できます。
- 機能が異なります: 以下の一般的な代替品で
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
:asm
goto
case
default
try-block
- パラメーターと戻り値の型はリテラル型 (つまり、一般的に言えば、非常に単純な型、通常はスカラーまたは集合体) である必要があります。
- 関数本体は非仮想かつ非常に単純である必要があります。
const
任意の関数で初期化できますが、 constexpr
非(式または非式でマークされてい constexpr
ない関数) で初期化するとコンパイラ エラーが生成されます。 constexpr
constexpr
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
) constexpr
。bool
その理由は、コンパイラがこのような関数を生成できないためです。
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
し、その型のオブジェクト (必ずしもオブジェクトである必要はありません) を関数に渡す必要があります。関数はテンプレートである必要があり、その型でエンコードされた値にアクセスできます。constexpr
constexpr
constexpr
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