web-dev-qa-db-ja.com

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

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

// preprocessor.
#define MEANING_OF_LIFE 42

// constants:
const int MeaningOfLife = 42;

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

リテラル値を返す関数を作成し、コードレビューを行った場合、return 5を書き込む代わりに定数値を宣言する必要があると誰かが教えてくれるようです。

309
Warren P

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

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

281
Goz

前書き

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

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

  • 開発者がこのエンティティで表現したいことは何ですか?
  • constant-expressionでコードが使用されることを盲目的に許可する必要があります。

constexprがなければ世界はどうなるでしょうか?

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

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

意図の欠如

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

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

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

最適化

"awesome"ライブラリ開発者として、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が必要なコンテキストで発生した関数の使用をすべて中断しました。

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


それで、なぜconstexprが必要なのでしょうか?

constexprの主な使用法は、intentを宣言することです。

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

129

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;
}
89
Konrad Rudolph

constexpr関数は本当にすてきで、C++に追加されました。しかし、あなたはそれが解決する問題の大部分がマクロで巧妙に回避できるという点で正しいです。

ただし、constexprの使用の1つには、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;
17
deft_code

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

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

編集:

SOをいじくり回した後、他の人がconstexprsで可能なことの someexamples を思いついたようです。

14
luke

「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
11
user2176127

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

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

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

3.14D + 42_i;
7
Motti

以前は、メタプログラミングを使用したパターンがありました。

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がなくても)。また、テンプレート引数として「正式な」定数式を使用する必要があるという問題もあります。

6
Kos

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

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

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

他のすべての答えは素晴らしいです、私はあなたがconstexprで驚くべきことをすることができる1つのことのクールな例を挙げたいです。 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;
}
2
Halcyon

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

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

以上

int arr[5];

ずっとメンテナンスしやすいからです。 constexprを使用すると、既存のメタプログラミング手法よりもはるかに高速に書き込みおよび読み取りができます。

1
Puppy

constexprを使用する場合:

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

いくつかの新しい最適化が可能になります。 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;
};

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

0
AshleysBrain