constexpr
との違いは何const
ですか?
- そのうちの1つだけを使用できるのはいつですか?
- いつ両方を使用でき、どのように選択すればよいですか?
両方のキーワードは、オブジェクトと関数の宣言で使用できます。オブジェクトに適用した場合の基本的な違いは次のとおりです。
const
オブジェクトを定数として宣言します。これは、一度初期化されると、そのオブジェクトの値が変更されないという保証を意味し、コンパイラーはこの事実を最適化に利用できます。また、初期化後に変更することを意図していないオブジェクトを変更するコードをプログラマーが作成するのを防ぐのにも役立ちます。
constexpr
標準が定数式と呼ぶもので使用するのに適したオブジェクトを宣言します。ただし、constexpr
これを行う方法はそれだけではないことに注意してください。
関数に適用した場合の基本的な違いは次のとおりです。
const
非静的メンバー関数にのみ使用でき、一般的な関数には使用できません。これにより、メンバー関数が非静的データメンバーを変更しないことが保証されます(とにかく変更できる可変データメンバーを除く)。
constexpr
コンストラクターだけでなく、メンバー関数と非メンバー関数の両方で使用できます。定数式で使用するのに適した関数を宣言します。コンパイラーは、関数が特定の基準(7.1.5 / 3,4)、最も重要なこと(†)を満たしている場合にのみそれを受け入れます。
return
ステートメントのみが許可されます。コンストラクターの場合、初期化リスト、typedef、および静的アサートのみが許可されます。(ただし= default
、= delete
許可されています。)asm
宣言、ステートメント、 andgoto
以外のラベルを持つステートメント、try-block、非リテラルの変数の定義タイプ、静的またはスレッドの保存期間の変数の定義、初期化が実行されない変数の定義。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によるものです。定数式には、「[…]整数型または列挙型のリテラル[…]でない限り、左辺値から右辺値への変更」を含む部分式を含めることはできません。以前は、これはすべてのリテラル型に当てはまると主張していました。]
関数を定数式での使用に適合させるには、明示的に宣言する必要があり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.オブジェクト宣言内。両方のキーワードが宣言される同じオブジェクトを参照している場合、これは必要ありません。constexpr
を意味しconst
ます。
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
(ポインターを宣言します-) to-const)。を削除する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
。
const
変数に適用され、コード内で変数が変更されるのを防ぎます。
constexpr
この式がコンパイル時定数値になることをコンパイラーに通知するため、配列の長さ、const
変数への割り当てなどの場所で使用できます。Oliによって提供されるリンクには、多くの優れた例があります。
基本的に、これらは完全に2つの異なる概念であり、一緒に使用できます(そして一緒に使用する必要があります)。
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
。その本体は非仮想である必要があり、typedefとstatic assertを除いて、単一のreturnステートメントのみで構成されている必要があります。その引数と戻り値はリテラル型でなければなりません。非定数式の引数で使用できますが、それが行われると、結果は定数式ではなくなります。
定数式関数は、パフォーマンスや型の安全性を犠牲にすることなく、マクロとハードコードされたリテラルを置き換えることを目的としています。
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
。これは、定数式または定数式引数を持つ定数式コンストラクターによって構築された右辺値で初期化する必要があります。
定数式オブジェクトは、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
。メンバー初期化リストを持つことができますが、typedefと静的アサートを除いて、本体は空である必要があります。その引数はリテラル型でなければなりません。
コンストラクターの引数がすべて定数式である場合、定数式コンストラクターを使用すると、コンパイラーはコンパイル時にオブジェクトを初期化できます。
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++」からのヒントconstexpr
:
constexpr
オブジェクトはconstであり、コンパイル中に既知の値で初期化されます。constexpr
関数は、コンパイル中に値がわかっている引数を指定して呼び出されると、コンパイル時の結果を生成します。constexpr
constexpr
オブジェクトと関数は、非オブジェクトと関数よりも幅広いコンテキストで使用できます。constexpr
オブジェクトまたは関数のインターフェースの一部です。との両方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配列を指定することはできません。
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.
}
};
Aconstexpr
は別の概念です。関数(メンバーまたは非メンバー)を、コンパイル時定数が引数として渡された場合にコンパイル時に評価できる関数としてマークします。たとえば、これを書くことができます。
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
、メンバー関数(メソッド)、演算子、さらにはコンストラクターにも適用できます。例えば。
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.
BjarneStroustrupによる「C++プログラミング言語第4版」の本によると、
• 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.4∗square(var); // error : var is not a constant expression
const double max3 = 1.4∗square(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であるためには、関数はかなり単純でなければなりません。値を計算する単なるreturnステートメントです。constexpr関数は非定数引数に使用できますが、それが行われると、結果は定数式ではなくなります。定数式を必要としないコンテキストでconstexpr関数を非定数式引数で呼び出すことができるため、本質的に同じ関数を2回定義する必要はありません。1回は定数式用、もう1回は変数用です。
いくつかの場所では、定数式は言語規則(たとえば、配列境界(§2.2.5、§7.3)、ケースラベル(§2.2.4、§9.4.2)、いくつかのテンプレート引数(§25.2)、およびconstexprを使用して宣言された定数)。その他の場合、コンパイル時の評価がパフォーマンスにとって重要です。パフォーマンスの問題とは関係なく、(状態が変更できないオブジェクトの)不変性の概念は重要な設計上の懸念事項です(§10.4)。
Aconst int var
は実行時に動的に値に設定でき、その値に設定されると、変更できなくなります。
Aは、実行時に動的に設定する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
。** know **
実行時のパフォーマンスを向上させるために、コンパイル時に可能な限り特定のことを目的とした新しいイノベーションがC++に導入されました。
上記の2つの重要な概念を含まない説明の試みは、幻覚です。
@ 0x499602d2がすでに指摘しているconst
ように、初期化後に値を変更できないことを保証するだけです。as 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
どの答えも、それがどのような副作用を持っているのか、あるいは実際にそれが何であるのかを正確に明らかにしているとは思いません。
constexpr
名前空間/ファイルスコープでconst
は、リテラルまたは式で初期化した場合は同じです。ただし、関数をconst
使用すると、任意の関数で初期化できますがconstexpr
、非constexpr(constexprまたは非constexpr式でマークされていない関数)で初期化すると、コンパイラエラーが発生します。constexpr
とは両方ともconst
変数の暗黙の内部リンケージです(実際には、-O1以上をコンパイルすると、リンクステージに到達するために存続static
せず、コンパイラに内部(ローカル)リンカーシンボルを発行するように強制しませんconst
。constexpr
-O1以上。これを行うのは、変数のアドレスを取得する場合のみです。ieでconst
表現さconstexpr
れていない限り、内部シンボルになります。extern
extern constexpr/const int i = 3;
使用する必要があります)。関数でconstexpr
は、関数が永続的にリンク段階に到達しないようにします(定義または-O0または-Ofastに関係なく)が、決して到達せず、extern
-O1inline
以上const
にstatic
のみこinline
の影響を及ぼします。const
/constexpr
変数が関数によって初期化される場合constexpr
、負荷は常に最適化フラグで最適化されますが、関数がstatic
またはのみの場合inline
、または変数が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
atブロックスコープは、非constexpr関数によって初期化された場合にエラーを生成するという点で同じように動作します。値もすぐに置き換えられます。
結局、その主な目的はCのインライン関数のようなものですが、この関数を使用してファイルスコープ変数を初期化する場合にのみ有効です(関数はCでは実行できませんが、ファイルの動的初期化が可能であるため、C++では実行できます-スコープ変数)。ただし、関数は、Cで使用extern/static
できる、を使用しても、グローバル/ローカルシンボルをリンカーにエクスポートすることはできません。inline
ブロックスコープの変数割り当て関数はconstexpr
、CおよびC ++を使用せずに、-O1最適化を使用するだけでインライン化できます。
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関数の呼び出しは、特定の条件が満たされた場合にコンパイル時の定数として取得されます。
注:関数呼び出しで関数のパラメーター変数に送信される引数、または複数のパラメーターがある場合はすべてのパラメーター変数に送信される引数。CEの場合、関数の戻り値はコンパイル時に計算されます。!!!
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関数であるためには、関数の戻り値の型と関数のパラメーターの型が「リテラル型」と呼ばれる型カテゴリーに含まれている必要があります。
constexpr関数は、暗黙的にインライン関数です。
constexpr関数は、定数式で呼び出す必要はありません。必須ではありません。この場合、コンパイル時に計算は行われません。通常の関数呼び出しのように扱われます。したがって、定数式が必要な場合、この式は使用できなくなります。
1)関数のパラメータで使用される型と関数の戻り値の型は、リテラル型である必要があります。
2)静的な有効期間を持つローカル変数を関数内で使用しないでください。
3)関数が有効な場合、コンパイル時に定数式を使用してこの関数を呼び出すと、コンパイラはコンパイル時に関数の戻り値を計算します。
4)コンパイラは関数のコードを確認する必要があるため、constexpr関数はほとんどの場合ヘッダーファイルに含まれます。
5)作成した関数をconstexpr関数にするには、関数の定義をヘッダーファイルに含める必要があります。したがって、そのヘッダーファイルを含むソースファイルには、関数の定義が表示されます。
通常、デフォルトのメンバー初期化では、定数型と整数型の静的データメンバーをクラス内で初期化できます。ただし、これを行うには、「const」と「integraltypes」の両方が必要です。
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
};
まず第一に、両方ともc++の修飾子です。constとして宣言された変数は初期化する必要があり、将来変更することはできません。したがって、通常、constとして宣言された変数は、コンパイル前でも値を持ちます。
ただし、constexprの場合は少し異なります。
constexprの場合、プログラムのコンパイル中に評価できる式を指定できます。
明らかに、constexperとして宣言された変数は、constのように将来変更することはできません。