C ++ 11でconstexpr機能を使用する必要があるのはいつですか?


337

「常に5を返す関数」を持つことは、「関数を呼び出す」という意味を壊したり弱めたりしているように思えます。理由があるか、この機能が必要であるか、C ++ 11にはないでしょう。なぜそこにあるのですか?

// preprocessor.
#define MEANING_OF_LIFE 42

// constants:
const int MeaningOfLife = 42;

// constexpr-function:
constexpr int MeaningOfLife () { return 42; }

私がリテラル値を返す関数を書いて、コードレビューにたどり着いたら、誰かが私に言ったので、return 5を書く代わりに定数値を宣言する必要があります。


28
を返す再帰関数を定義できますconstexprか?もしそうなら、私は使用法を見ることができます。
ereOn

20
質問では、「コンパイラーがコンパイル時に関数を評価できるかどうかをコンパイラーが推測できるのに、なぜ新しいキーワード(!)を導入するのか」と述べるべきだと思います。「キーワードによって保証される」というのはいいことですが、可能な場合はいつでも、キーワードを必要とせずに保証されるようにしたいと思います。
Kos

6
@Kos:C ++の内部に精通している人はおそらくあなたの質問を好むでしょうが、私の質問は、以前にCコードを記述したことがあるが、C ++ 2011のキーワードやC ++コンパイラの実装の詳細に精通していない人の観点からのものです。 。コンパイラの最適化と定数式の推定について推論できることは、これよりも上級者向けの質問の対象です。
ウォーレンP

8
@Kos私はあなたと同じように考えていました、そして私が思いついた答えは、constexprなしで、コンパイラが実際に関数のコンパイル時に評価されることを(簡単に)どうやって知るのですか?アセンブリの出力をチェックして何が行われたかを確認できると思いますが、最適化が必要であることをコンパイラに伝える方が簡単です。何らかの理由で最適化できない場合は、適切なコンパイルが行われます。最適化が期待される場所で最適化に失敗するのではなく、エラー。
Jeremy Friesner、2013年

3
@コス:同じことを言うことができますconst。実際、必須の意図役に立ちます!配列の次元は標準的な例です。
オービットのライトネスレース2013年

回答:


302

それがもう少し複雑なことをするとします。

constexpr int MeaningOfLife ( int a, int b ) { return a * b; }

const int meaningOfLife = MeaningOfLife( 6, 7 );

これで、良好な読みやすさを維持し、定数を数値に設定するよりもわずかに複雑な処理を可能にしながら、定数まで評価できるものがあります。

それはあなたが何をしているのかがより明らかになるので、基本的には保守性に良い助けを提供します。テイクmax( a, b )例えば:

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

そこでは非常に単純な選択ですがmax、定数値で呼び出すと、実行時ではなくコンパイル時に明示的に計算されることを意味します。

別の良い例はDegreesToRadians関数です。誰もがラジアンよりも読みやすい学位を見つけます。180度がラジアンであることはご存知かもしれませんが、次のように書くとより明確になります。

const float oneeighty = DegreesToRadians( 180.0f );

ここにたくさんの良い情報があります:

http://en.cppreference.com/w/cpp/language/constexpr


18
コンパイル時に値を計算してみるようコンパイラーに指示することの優れた点。特定の最適化が指定されているときにconstがこの機能を提供しないのはなぜですか?それともそうですか?
TamusJRoyce、2011年

11
@Tamus:多くの場合、そうなりますが、義務はありません。constexprはコンパイラに義務を課し、それができない場合はエラーを吐き出します。
Goz

20
今見えます。罪(0.5)は別のものです。これはCマクロをきれいに置き換えます。
Warren P

10
これを新しいインタビューの質問として見ることができます。constキーワードとconstexprキーワードの違いを説明してください。
Warren P

2
この点を自分で文書化する方法として、上記と同様のコードを作成し、関数を「constexpr」ではなく「const」にしました。Clang3.3、-pedantic-errorsおよび-std = c ++ 11を使用しているので、後者はコンパイルされないはずです。「constexpr」の場合と同様にコンパイルして実行しました。これはclang拡張機能であると思いますか、またはこの投稿への回答以来、C ++ 11仕様に微調整が加えられていますか?
アーバレスト2013

144

前書き

constexpr定数式を必要とするコンテキストで何かを評価できることを実装に伝える方法として導入されませんでした。適合する実装は、C ++ 11より前にこれを証明することができました。

実装が証明できないものは、特定のコードの意図です。

  • 開発者がこのエンティティで表現したいことは何ですか?
  • たまたまコードが定数式で使用できるようにすべきでしょうか?

世界には何がないconstexprでしょうか?

ライブラリを開発していて、区間内のすべての整数の合計を計算できるようにしたいことに気付いたとします(0,N]

int f (int n) {
  return n > 0 ? n + f (n-1) : n;
}

意図の欠如

コンパイラーは、渡された引数が変換中にわかっている場合、上記の関数が定数式で呼び出し可能であることを簡単に証明できます。しかし、これを意図として宣言していません-たまたまそうなのです。

さて、誰かがやって来て、あなたの関数を読み、コンパイラと同じ分析をします。「ああ、この関数は定数式で使える!」、次のコードを記述します。

T arr[f(10)]; // freakin' magic

最適化

あなたは、「素晴らしい」ライブラリ開発者として、f呼び出されたときに結果をキャッシュすることを決定します。同じ値のセットを何度も計算したい人はいますか?

int func (int n) { 
  static std::map<int, int> _cached;

  if (_cached.find (n) == _cached.end ()) 
    _cached[n] = n > 0 ? n + func (n-1) : n;

  return _cached[n];
}

結果

ばかげた最適化を導入することにより、たまたま定数式が必要とされるコンテキストにある関数のすべての使用法を壊しました。

関数がconstant-expressionで使用可能であると約束したことは決してありませんconstexpr。そうでなければ、そのような約束を提供する方法はありません。


では、なぜ必要なのconstexprでしょうか?

constexprの主な用途は、意図を宣言することです。

エンティティがマークされていない場合constexpr- 定数式で使用することは意図されていませんでした。そうである場合でも、コンパイラーに依存してこのようなコンテキストを診断します(意図を無視するため)。


25
C ++ 14およびC ++ 17での最近の変更により、constexpr式で使用される言語の範囲が大幅に拡大されたため、これはおそらく正しい答えです。言い換えれば、ほとんど何でも注釈を付けることができますconstexpr(多分いつの日か、これが原因で単にconstexprなくなるでしょうか?)、いつ使用するかどうかの基準がない限り、ほとんどすべてのコードがそのように記述されます。
alecov

4
@alecov間違いなくすべてI/Oではsyscallありません... 、そして間違いなくdynamic memory allocationとしてマークするconstexprことできませconstexpr。すべてでありません。
JiaHao Xu 2018

1
@alecov一部の関数は実行時に実行されることを意図しており、コンパイル時にこれを実行しても意味がありません。
JiaHao Xu 2018

1
私もこの答えが一番好きです。コンパイル時の評価はきちんとした最適化ですが、実際に得られるのconstexprは、ある種の動作の保証です。ちょうどのようにconst
トマーシュザト-モニカを復活させる

このconstexprなしのバージョンを許可するコンパイラーは、 int f (int n) { return n > 0 ? n + f (n-1) : n;} T arr[f(10)]; どこでもコンパイルできませんか?
Jer

91

取るstd::numeric_limits<T>::max():何らかの理由で、これはメソッドです。constexprここで有益でしょう。

別の例:C配列(または std::array別の配列と同じ大きさの)。現時点でこれを行う方法は次のとおりです。

int x[10];
int y[sizeof x / sizeof x[0]];

しかし、以下のように記述できる方がよいでしょう。

int y[size_of(x)];

のおかげでconstexpr、次のことができます。

template <typename T, size_t N>
constexpr size_t size_of(T (&)[N]) {
    return N;
}

1
+1を使用すると、テンプレートを適切に使用できますが、constexprがなくてもまったく同じように機能します。
Kos

21
@コス:いいえ。ランタイム値を返します。 constexprコンパイラが関数にコンパイル時の値を返すようにします(可能な場合)。
deft_code

14
@Kos:constexpr関数呼び出しの結果がコンパイル時の定数であるかどうかに関係なく、配列サイズ宣言で、またはテンプレート引数として使用することはできません。これら2つは基本的に唯一の使用例ですconstexprが、少なくともテンプレート引数の使用例は重要です。
Konrad Rudolph

2
「なんらかの理由で、これはメソッドです」:C ++ 03にはコンパイル時の整数しかなく、他のコンパイル時の型はないため、C ++ 11より前の任意の型に対してはメソッドのみが機能します。
セバスチャンマッハ

5
@LwCuiいいえ、それは「大丈夫」ではありません。GCCは特定の事柄に関してデフォルトで単なる緩いです。-pedanticオプションを使用すると、エラーとしてフラグが立てられます。
Konrad Rudolph

19

constexpr関数は本当に素晴らしく、c ++へのすばらしい追加です。しかし、それが解決する問題のほとんどは、マクロを使って上手に回避できるという点であなたは正しいです。

ただし、の使用の1 constexprつには、C ++ 03に対応する型付き定数がありません。

// This is bad for obvious reasons.
#define ONE 1;

// This works most of the time but isn't fully typed.
enum { TWO = 2 };

// This doesn't compile
enum { pi = 3.1415f };

// This is a file local lvalue masquerading as a global
// rvalue.  It works most of the time.  But May subtly break
// with static initialization order issues, eg pi = 0 for some files.
static const float pi = 3.1415f;

// This is a true constant rvalue
constexpr float pi = 3.1415f;

// Haven't you always wanted to do this?
// constexpr std::string awesome = "oh yeah!!!";
// UPDATE: sadly std::string lacks a constexpr ctor

struct A
{
   static const int four = 4;
   static const int five = 5;
   constexpr int six = 6;
};

int main()
{
   &A::four; // linker error
   &A::six; // compiler error

   // EXTREMELY subtle linker error
   int i = rand()? A::four: A::five;
   // It not safe use static const class variables with the ternary operator!
}

//Adding this to any cpp file would fix the linker error.
//int A::four;
//int A::six;

12
「非常に微妙なリンカーエラー」を明確にしていただけませんか?または、少なくとも説明へのポインタを提供しますか?
enobayram 2013

4
@enobayram、三項演算子はオペランドのアドレスを取ります。それはコードからは明らかではありません。すべてが正常にコンパイルされますが、アドレスがfour解決されないため、リンクは失敗します。私は自分のstatic const変数のアドレスを誰が取っているのかを本当に掘り下げる必要がありました。
deft_code 2013

23
「これは明らかな理由で悪い」:最も明白な理由はセミコロンですよね?
TonyK 2013

4
「非常に微妙なリンカーエラー」は完全に戸惑いました。どちらfourfive対象外です。
Steven Lu

3
新しいenum classタイプも参照してください。列挙型の問題のいくつかが修正されます。
ninMonkey 2013

14

私が読んだことから、constexprの必要性はメタプログラミングの問題から来ています。特性クラスには、関数として表される定数がある場合があります。numeric_limits:: max()と考えてください。constexprを使用すると、これらのタイプの関数をメタプログラミングで使用したり、配列の境界などとして使用したりできます。

私の頭の上の別の例は、クラスインターフェイスの場合、派生型がいくつかの操作のために独自の定数を定義することを望む場合があるということです。

編集:

SO を試してみたところ、constexprsで何ができるかについていくつかの が考え出されたようです。


「インターフェイスの一部になるには、関数でなければなりません」?
Daniel Earwicker、2011年

これの有用性を確認できたので、C ++ 0xについてもう少し興奮します。よく考えられているようです。私は彼らがそうでなければならないことを知っていました。それらの言語標準の超ギークはめったにランダムなことをしません。
Warren P

ラムダ、スレッディングモデル、initializer_list、右辺値参照、可変個のテンプレート、新しいバインドオーバーロードについて、私はもっとわくわくしています...
2011年

1
そうそう、でも私はすでに他のいくつかの言語のラムダ/クロージャを理解しています。 constexprは、強力なコンパイル時式評価システムを備えたコンパイラーで特に役立ちます。C ++には、実際にはそのドメインにピアはありません。(それはC ++ 11、IMHOに対する強い賞賛です)
Warren P

11

「Going Native 2012」でのStroustrupのスピーチから:

template<int M, int K, int S> struct Unit { // a unit in the MKS system
       enum { m=M, kg=K, s=S };
};

template<typename Unit> // a magnitude with a unit 
struct Value {
       double val;   // the magnitude 
       explicit Value(double d) : val(d) {} // construct a Value from a double 
};

using Speed = Value<Unit<1,0,-1>>;  // meters/second type
using Acceleration = Value<Unit<1,0,-2>>;  // meters/second/second type
using Second = Unit<0,0,1>;  // unit: sec
using Second2 = Unit<0,0,2>; // unit: second*second 

constexpr Value<Second> operator"" s(long double d)
   // a f-p literal suffixed by ‘s’
{
  return Value<Second> (d);  
}   

constexpr Value<Second2> operator"" s2(long double d)
  // a f-p literal  suffixed by ‘s2’ 
{
  return Value<Second2> (d); 
}

Speed sp1 = 100m/9.8s; // very fast for a human 
Speed sp2 = 100m/9.8s2; // error (m/s2 is acceleration)  
Speed sp3 = 100/9.8s; // error (speed is m/s and 100 has no unit) 
Acceleration acc = sp1/0.5s; // too fast for a human

2
この例は、Stroustrupのペーパー 『Software Development for Infrastructure』にもあります。
Matthieu Poullet 2013

clang-3.3:エラー:constexpr関数の戻り値型 'Value <Second>'はリテラル型ではありません
Mitja

これはいいですが、このようなコードにリテラルを入れるのはだれですか。インタラクティブな計算機を作成している場合は、コンパイラーに「ユニットをチェック」させることは理にかなっています。
bobobobo 2013

5
@boboboboまたは、火星気候オービター用のナビゲーションソフトウェアを作成していた場合、多分:)
Jeremy Friesner

1
コンパイルするには-1.リテラル接尾辞にアンダースコアを使用します。2. 100_mに演算子 "" _mを追加します。3. 100.0_mを使用するか、unsigned long longを受け入れるオーバーロードを追加します。4. Valueコンストラクターconstexprを宣言します。5.対応する演算子/を次のようにValueクラスに追加します。constexpr auto operator /(const Value <Y>&other)const {return Value <Unit <TheUnit :: m-Value <Y> :: TheUnit :: m、TheUnit :: kg-値<Y> :: TheUnit :: kg、TheUnit :: s-値<Y> :: TheUnit :: s >>(val / other.val); }。TheUnitは、Valueクラス内に追加されたUnitのtypedefです。
0kcats 16

8

別の用途(まだ言及されていません)はconstexprコンストラクタです。これにより、実行時に初期化する必要のないコンパイル時定数を作成できます。

const std::complex<double> meaning_of_imagination(0, 42); 

これをユーザー定義リテラルと組み合わせると、リテラルユーザー定義クラスを完全にサポートできます。

3.14D + 42_i;

6

以前はメタプログラミングのパターンがありました:

template<unsigned T>
struct Fact {
    enum Enum {
        VALUE = Fact<T-1>*T;
    };
};

template<>
struct Fact<1u> {
    enum Enum {
        VALUE = 1;
    };
};

// Fact<10>::VALUE is known be a compile-time constant

私は信じてconstexprあなたが専門、SFINAEとスタッフとテンプレートとの奇妙な構造を必要とせずにこのような構築物を書いてみましょうするために導入されました-しかし、あなたは実行時の関数を書くと思いますまったく同じ、しかし結果は、コンパイルして決定されることを保証して-時間。

ただし、次のことに注意してください。

int fact(unsigned n) {
    if (n==1) return 1;
    return fact(n-1)*n;
}

int main() {
    return fact(10);
}

これをでコンパイルすると、コンパイル時に実際に評価されるg++ -O3ことfact(10)がわかります!

VLA対応のコンパイラー(つまり、C99モードのCコンパイラーまたはC99拡張機能を備えたC ++コンパイラー)を使用すると、次のことができる場合もあります。

int main() {
    int tab[fact(10)];
    int tab2[std::max(20,30)];
}

しかし、それは現時点では非標準のC ++です- constexprこれに対処する方法のように見えます(上記の場合、VLAがなくても)。また、テンプレートの引数として「形式的な」定数式を使用する必要があるという問題もあります。


ファクト関数はコンパイル時に評価されません。これはconstexprである必要があり、returnステートメントは1つだけでなければなりません。
Sumant

1
@Sumant:コンパイル時に評価する必要がないことは正しいですが、そうです!コンパイラーで実際に何が起こるかについて言及していました。最近のGCCでコンパイルして、結果のasmを確認し、信じられない場合は自分で確認してください!
Kos

追加しようとするstd::array<int, fact(2)>と、fact()がコンパイル時に評価されないことがわかります。GCCオプティマイザがうまく機能しているだけです。

1
それは私が言ったことです...私は本当にそれが不明瞭ですか?最後の段落を参照してください
コス

5

プロジェクトをc ++ 11に切り替え始めたばかりで、同じ操作を実行する別の方法をクリーンアップするconstexprの完全に良い状況に遭遇しました。ここでの重要な点は、constexprと宣言されている場合にのみ、関数を配列サイズ宣言に配置できることです。これが私が関わっているコードの領域を前進させるのに非常に役立つことがわかる多くの状況があります。

constexpr size_t GetMaxIPV4StringLength()
{
    return ( sizeof( "255.255.255.255" ) );
}

void SomeIPFunction()
{
    char szIPAddress[ GetMaxIPV4StringLength() ];
    SomeIPGetFunction( szIPAddress );
}

4
これは、次のように書くこともできます。const size_t MaxIPV4StringLength = sizeof( "255.255.255.255");
Superfly Jon

static inline constexpr const autoおそらく良いです。
JiaHao Xu 2018

3

他のすべての答えは素晴らしいですが、私はconstexprを使ってあなたができる素晴らしいことの素晴らしい例を挙げたいと思います。See-Phit(https://github.com/rep-movsd/see-phit/blob/master/seephit.h)は、コンパイル時のHTMLパーサーおよびテンプレートエンジンです。これは、HTMLを入れたり、操作可能なツリーを取り出したりできることを意味します。コンパイル時に解析を行うと、パフォーマンスが少し向上します。

githubページの例から:

#include <iostream>
#include "seephit.h"
using namespace std;



int main()
{
  constexpr auto parser =
    R"*(
    <span >
    <p  color="red" height='10' >{{name}} is a {{profession}} in {{city}}</p  >
    </span>
    )*"_html;

  spt::tree spt_tree(parser);

  spt::template_dict dct;
  dct["name"] = "Mary";
  dct["profession"] = "doctor";
  dct["city"] = "London";

  spt_tree.root.render(cerr, dct);
  cerr << endl;

  dct["city"] = "New York";
  dct["name"] = "John";
  dct["profession"] = "janitor";

  spt_tree.root.render(cerr, dct);
  cerr << endl;
}

1

あなたの基本的な例は、定数自体と同じ議論を彼に提供します。使用する理由

static const int x = 5;
int arr[x];

以上

int arr[5];

保守性がずっと高いからです。constexprの使用は、既存のメタプログラミング手法よりもはるかに高速で書き込みおよび読み取りが可能です。


0

それはいくつかの新しい最適化を可能にすることができます。 const伝統的には型システムのヒントであり、最適化に使用することはできません(たとえば、constメンバー関数はconst_cast法的にオブジェクトを変更constできるため、最適化のために信頼することはできません)。

constexpr関数への入力がconstである場合、式は実際に定数であることを意味します。考慮してください:

class MyInterface {
public:
    int GetNumber() const = 0;
};

これが他のモジュールで公開されている場合、コンパイラーはGetNumber()呼び出されるたびに異なる値を返さないことを信頼できません-間に非const呼び出しがなくても連続して- const実装でキャストされた可能性があるためです。(明らかに、これを行ったプログラマは誰でも撃たれるべきですが、言語はそれを許しているので、コンパイラはルールを守らなければなりません。)

追加constexpr

class MyInterface {
public:
    constexpr int GetNumber() const = 0;
};

コンパイラは、の戻り値GetNumber()がキャッシュされる最適化を適用し、への追加の呼び出しを排除できるようになりましたGetNumber()。これconstexprは、戻り値が変更されないことをより強力に保証するためです。


実際に最適化で使用const できます... IIRCの後でさえconst定義された値を変更することは未定義の動作const_castです。constメンバー関数については一貫していると思いますが、それを標準で確認する必要があります。これは、コンパイラーがそこで最適化を安全に実行できることを意味します。
Kos

1
@ウォーレン:最適化が実際に行われるかどうかは問題ではなく、許可されるだけです。@Kos:元のオブジェクトがconst(vs. )と宣言されていない場合、ポインタ/参照のconstを削除して変更しても安全であるということは、あまり知られていない微妙な問題です。そうしないと、常に未定義の動作を呼び出し、役に立たなくなります:)この場合、コンパイラは元のオブジェクトの一貫性に関する情報を持っていないため、それを知ることができません。int xconst int xconst_castconst_cast
AshleysBrain、2011年

@Kosここではconst_castだけが問題だとは思いません。constメソッドは、グローバル変数の読み取りおよび変更を許可されています。逆に、別のスレッドの誰かが呼び出し間のconstオブジェクトを変更することもできます。
enobayram 2013

1
「= 0」はここでは無効であるため、削除する必要があります。私はそれを自分で行いますが、それがSOプロトコルに準拠しているかどうかはわかりません。
KnowItAllWannabe 2014年

どちらの例も無効です。最初の例(int GetNumber() const = 0;)はGetNumber()メソッドvirtual を宣言する必要があります。2番目の(constexpr int GetNumber() const = 0;)は有効ではありません。純粋な指定子(= 0)はメソッドが仮想であることを意味しますが、constexprは仮想であってはなりません(ref:en.cppreference.com/w/cpp/language/constexpr
stj

-1

いつ使用するかconstexpr

  1. コンパイル時定数があるときはいつでも。

私はあなたに同意しますが、この答えは、なぜ constexprプリプロセッサマクロやに優先する必要があるのを説明していませんconst
Sneftel

-3

次のような場合に便利です

// constants:
const int MeaningOfLife = 42;

// constexpr-function:
constexpr int MeaningOfLife () { return 42; }

int some_arr[MeaningOfLife()];

これを特性クラスなどと結びつけると、非常に便利になります。


4
あなたの例では、それは単純な定数に対してゼロの利点を提供するので、それは実際には質問に答えません。
jalf

これは不自然な例です。たとえば、MeaningOfLife()が他のどこかから値を取得した場合を想像してください。別の関数や#defineまたはseries therofなどです。あなたはそれが何を返すかわからないかもしれません、それはライブラリコードかもしれません。他の例として、constexpr size()メソッドを持つ不変のコンテナーを想像してみてください。これで、int arr [container.size()]を実行できます。
plivesey、2011年

2
@pliveseyでは、より良い例を使用して回答を編集してください。
ムケシュ2017
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.