違いは何だconstexpr
とはconst
?
- いつ使用できるのですか?
- いつ両方を使用できますか、どのように選択すればよいですか?
boost/hana
いくつかENLIGHTできるライブラリをconstexpr
使用できる問題をconstexpr
、どこであなたがすることはできません:boost.org/doc/libs/1_69_0/libs/hana/doc/html/...
constexpr
当時は
違いは何だconstexpr
とはconst
?
boost/hana
いくつかENLIGHTできるライブラリをconstexpr
使用できる問題をconstexpr
、どこであなたがすることはできません:boost.org/doc/libs/1_69_0/libs/hana/doc/html/...
constexpr
当時は
回答:
どちらのキーワードも、関数だけでなくオブジェクトの宣言にも使用できます。オブジェクトに適用した場合の基本的な違いは次のとおりです。
const
オブジェクトを定数として宣言します。これは、いったん初期化されると、そのオブジェクトの値が変更されないという保証を意味し、コンパイラーはこの事実を最適化に利用できます。また、初期化後に変更することを意図していないオブジェクトを変更するコードをプログラマが作成するのを防ぐのにも役立ちます。
constexpr
標準が定数式と呼ぶもので使用するのに適したオブジェクトを宣言します。ただしconstexpr
、これが唯一の方法ではありません。
関数に適用した場合の基本的な違いは次のとおりです。
const
非静的メンバー関数にのみ使用でき、関数全般には使用できません。これにより、メンバー関数が非静的データメンバーを変更しないことが保証されます。
constexpr
メンバー関数と非メンバー関数の両方、およびコンストラクターで使用できます。定数式での使用に適した関数を宣言します。コンパイラは、関数が特定の基準(7.1.5 / 3,4)を満たしている場合にのみそれを受け入れます。最も重要なのは(†):
return
許可されるステートメントは1つだけです。コンストラクターの場合、許可されるのは初期化リスト、typedef、および静的アサートのみです。(= default
と= delete
が、あまりにも、許可されています。)asm
宣言、goto
ステートメント、case
and 以外のラベルを持つステートメントdefault
、try-block、非リテラルの変数の定義タイプ、静的またはスレッドストレージ期間の変数の定義、初期化が実行されない変数の定義。上記のように、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によるものです。定数式には、「整数値または列挙型のglvalue […]を除いて、左辺値から右辺値への変更を含まない部分式を含めることはできません。」以前は、これはすべてのリテラル型に当てはまると主張していました。]
以下のための関数が定数式で使用するために適合すること、それがなければなりません明示的に宣言します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
(それがpointer-を宣言しますto-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
関数として引き続き使用できます。
constexpr
、通常の変数などの非定数式で関数を呼び出す場合、これは完全に合法であり、その関数は他の関数と同様に使用されるということです。コンパイル時に評価されません(評価できないため)。多分それは明白だと思うかもしれませんが、宣言された関数constexpr
が常にコンパイル時に評価されると述べた場合、間違った方法で解釈される可能性があります。
constexpr
関数ではなくオブジェクトについて話していました。constexpr
オブジェクトについては、値のコンパイル時の評価を強制するものとしてconstexpr
、関数については、コンパイル時または実行時に関数を適切に評価できるものとして考えるのが好きです。
mutable
された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と静的アサートを除き、単一の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
Scott Meyers 著「Effective Modern C ++」からのヒントconstexpr
:
constexpr
オブジェクトはconstであり、コンパイル時に既知の値で初期化されます。constexpr
関数は、コンパイル時に値がわかっている引数を指定して呼び出されると、コンパイル時の結果を生成します。constexpr
オブジェクトと関数は、非constexpr
オブジェクトと関数よりも幅広いコンテキストで使用できます。constexpr
オブジェクトまたは関数のインターフェースの一部です。Bjarne Stroustrupによる「The C ++ Programming Language 4th Editon」の本によると
• 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)。
両方とも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
キーワード手段方法は、それらのメンバー(非static)フィールドの値を変更することができません。例えば。
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 ++関数であり、定数でない引数が渡された場合でも呼び出すことができます。ただし、その場合は、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.
constexpr int
存在しますが、スペルされていますconst int
Aは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
。** know **
実行時のパフォーマンスを向上させるために、コンパイル時にできる限り特定のことを行うことを意図した新しい革新がC ++に導入されました。
どんな回答でも、それが何の副作用を持っているのか、それが何であるのかをはっきりさせているとは思いません。
constexpr
またconst
、namespace / file-scopeは、リテラルまたは式で初期化した場合と同じです。しかし、関数を使用const
すると、任意の関数で初期化できconstexpr
ますが、非constexpr(constexprまたは非constexpr式でマークされていない関数)で初期化すると、コンパイラエラーが生成されます。どちらconstexpr
とconst
暗黙的に内部結合が(まあ、実際、彼らは-O1と強いコンパイル場合はリンク段階に到達するために存続していない、と変数のためのものであるstatic
ため、内部(ローカル)リンカシンボルを放出するようにコンパイラに強制していないconst
か、constexpr
ときに-O1または強く、それがこれを行う唯一の時間は、変数のアドレスを取る場合である。const
とconstexpr
で表現しない限り、内部シンボルになりますextern
つまり、extern constexpr/const int i = 3;
使用する必要があります)。機能では、constexpr
機能が永久に(関係なく、リンク段階に達することがないですextern
かinline
、定義または-O0または-Ofast中)に対しconst
ませ決して、そしてstatic
そしてinline
だけ-O1以上にこの効果を持っています。とき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 ++では可能です)機能を除き、スコープ変数が)、でも使用して、同様にリンカにグローバル/ローカルシンボルをエクスポートすることはできませんextern/static
、とされますが、可能性inline
Cに。ブロックスコープの変数割り当て関数はconstexpr
、CおよびC ++ を使用せずに-O1最適化を使用するだけでインライン化できます。
まず、どちらもc ++の修飾子です。constとして宣言された変数は初期化する必要があり、将来変更することはできません。したがって、一般的にconstとして宣言された変数は、コンパイル前でも値を持ちます。
ただし、constexprの場合は少し異なります。
constexprの場合、プログラムのコンパイル中に評価できる式を指定できます。
constexperとして宣言された変数は、constのように将来変更できないことは明らかです。
constexpr
コンパイル時の定数を作成します。const
単に値を変更できないことを意味します。