53

関連: constexpr を返す関数がコンパイルされない

constexpr は、C++11 での有用性が制限されているように感じます。これは、そうでなければ同じシグネチャを持つが、1 つが constexpr で、もう 1 つが constexpr でない 2 つの関数を定義できないためです。つまり、たとえば、constexpr 引数のみを取る constexpr std::string コンストラクターと、非 constexpr 引数用の非 constexpr std::string コンストラクターがあれば非常に便利です。もう 1 つの例は、state を使用してより効率的にすることができる、理論的に複雑な関数です。これを constexpr 関数で簡単に行うことはできないため、次の 2 つの選択肢があります。constexpr 以外の引数を渡すと非常に遅い constexpr 関数を使用するか、constexpr を完全に放棄するか (または 2 つの別個の関数を記述し、ただし、どのバージョンを呼び出すべきかわからない場合があります)。

したがって、私の質問は次のとおりです。

標準準拠の C++11 実装で、引数が constexpr であることに基づいて関数のオーバーロードを許可することは可能ですか?それとも標準を更新する必要がありますか? 許可されていない場合、意図的に許可されていませんでしたか?


@NicolBolas: を にマップする関数があるとしenumますstd::string。これを行う最も簡単な方法は、 myenumが から0に進むと仮定して、結果で満たされたn - 1サイズの配列を作成することです。n

を作成して戻り時にstatic constexpr char const * []構築する(関数を呼び出すたびにオブジェクトを作成するコストを支払う) ことも、 を作成して検索した値を返すこともできます。関数を呼び出します。より良い解決策は、コンパイル時にメモリ内に を作成することだと思われますが (現在 で行われているのと同様)、これを行う唯一の方法は、コンストラクタに引数があることを警告することです。std::stringstd::stringstatic std::string const []std::stringstd::stringchar const *constexpr

コンストラクター以外の例については、要件を無視できる(したがって非関数を作成する) ことができれば、より効率的な関数を作成std::stringできる例を見つけるのは非常に簡単だと思います。次のスレッドを考えてみてください: constexpr の質問、なぜこれら 2 つの異なるプログラムが g++ でこれほど異なる時間で実行されるのですか?constexprconstexpr

引数を指定して呼び出した場合、関数呼び出しを完全に最適化するコンパイラよりも優れた結果fibを出すconstexprことはできません。しかし、引数なしで呼び出す場合はfib、メモ化 (状態が必要) などを実装する独自のバージョンを呼び出して、引数constexprを渡した場合のコンパイル時間と同様の実行時間を取得したい場合があります。 constexpr.

4

8 に答える 8

39

この機能が欠けていることに同意します - 私もそれが必要です。例:

double pow(double x, int n) {
    // calculate x to the power of n
    return ...
}

static inline double pow (double x, constexpr int n) {
    // a faster implementation is possible when n is a compile time constant
    return ...
}

double myfunction (double a, int b) {
    double x, y;
    x = pow(a, b);  // call version 1 unless b becomes a compile time constant by inlining
    y = pow(a, 5),  // call version 2
    return x + y;
}

今、私はテンプレートでこれをしなければなりません:

template <int n>
static inline double pow (double x) {
    // fast implementation of x ^ n, with n a compile time constant
    return ...
}

これは問題ありませんが、過負荷の機会を逃しています。他の人が使用できるようにライブラリ関数を作成すると、n がコンパイル時の定数であるかどうかに応じて、ユーザーが異なる関数呼び出しを使用する必要があり、コンパイラが n を a に減らしたかどうかを予測するのが難しい場合があります。時定数をコンパイルするかどうか。

于 2012-04-01T08:23:18.723 に答える
10

編集:以下で説明するトリックは、もはや動作することが保証されていません!

オーバーロードを使用して検出constexprを行うことはできませんが(他の人がすでに回答したように)、オーバーロードはそれを行う1つの方法にすぎません。

典型的な問題は、実行時のパフォーマンスを改善できるもの (たとえば、非constexpr関数を呼び出したり、結果をキャッシュしたりする) をconstexpr関数で使用できないことです。そのため、2 つの異なるアルゴリズムになる可能性があります。1 つは効率は劣りますが として書き込み可能でありconstexpr、もう 1 つは高速に実行するように最適化されていますが ではありませんconstexprconstexpr次に、コンパイラがランタイム値のアルゴリズムを選択しないようにし、その逆も同様に行います。

constexprこれは、それに基づいて「手動で」検出および選択し、プリプロセッサ マクロを使用してインターフェイスを短縮することで実現できます。

まず、2 つの関数を用意します。一般に、関数は異なるアルゴリズムで同じ結果に到達する必要があります。ここでは、アイデアをテストして説明するために、決して同じ答えを返さない 2 つのアルゴリズムを選択します。

#include <iostream>     // handy for test I/O
#include <type_traits>  // handy for dealing with types

// run-time "foo" is always ultimate answer
int foo_runtime(int)
{
    return 42;
}

// compile-time "foo" is factorial
constexpr int foo_compiletime(int num)
{
      return num > 1 ? foo_compiletime(num - 1) * num : 1;
}

次に、引数がコンパイル時の定数式であることを検出する方法が必要です。そのようなコンパイラ固有の方法を使用したくない場合は __builtin_constant_p、標準 C++ でもそれを検出する方法があります。次のトリックは Johannes Schaub によって発明されたと確信していますが、引用が見つかりません。非常に素晴らしく明確なトリック。

template<typename T> 
constexpr typename std::remove_reference<T>::type makeprval(T && t) 
{
    return t;
}

#define isprvalconstexpr(e) noexcept(makeprval(e))

演算子はコンパイル時に動作する必要があるため、noexcept演算子に基づく分岐はほとんどのコンパイラによって最適化されます。これで、引数の constexprness に基づいてアルゴリズムを選択し、それをテストする「foo」マクロを作成できます。

#define foo(X) (isprvalconstexpr(X)?foo_compiletime(X):foo_runtime(X))

int main(int argc, char *argv[])
{
    int a = 1;
    const int b = 2;
    constexpr int c = 3;
    const int d = argc;

    std::cout << foo(a) << std::endl;
    std::cout << foo(b) << std::endl;
    std::cout << foo(c) << std::endl;
    std::cout << foo(d) << std::endl;
}

予想される出力は次のとおりです。

42
2
6
42

私が試したいくつかのコンパイラでは、期待どおりに動作します。

于 2016-01-31T04:48:37.997 に答える
8

C++11 には "constexpr オーバーロード" のようなものはありませんが、GCC/Clang__builtin_constant_p組み込みを引き続き使用できます。GCC と Clang の両方が定数整数指数の pow を既に最適化できるため、この最適化はあまり役に立ちませんdouble pow(double)が、多重精度またはベクトル ライブラリを作成する場合、この最適化は機能するはずです。

この例を確認してください:

#define my_pow(a, b) (__builtin_constant_p(b) ? optimized_pow(a, b) : generic_pow(a, b))

double generic_pow(double a, double b);

__attribute__((always_inline)) inline double optimized_pow(double a, double b) {
    if (b == 0.0) return 1.0;
    if (b == 1.0) return a;
    if (b == 2.0) return a * a;
    if (b == 3.0) return a * a * a;
    if (b == 4.0) return a * a * a * a;

    return generic_pow(a, b);
}

double test(double a, double b) {
    double x = 2.0 + 2.0;
    return my_pow(a, x) + my_pow(a, b);
}

この例my_pow(a, x)ではa*a*a*a(デッド コードの除去のおかげで) に拡張され、事前チェックなしmy_pow(a, b)で直接呼び出しに拡張されます。generic_pow

于 2014-02-27T08:27:46.417 に答える
8

constexpr引数ではなく、結果が存在するかどうかに基づいてオーバーロードする必要があります。

Aconst std::stringは、リテラルへのポインターを格納することができますが、それが書き込まれることは決してないことを知っています ( the を使用しconst_castて削除する必要があり、それは既に未定義の動作です)。破棄中にバッファを解放することを禁止するために、ブール値フラグを格納する必要があるだけです。conststd::string

ただし、非const文字列は、引数から初期化された場合でも、constexpr引数の書き込み可能なコピーが必要なため動的割り当てが必要であり、仮想constexprコンストラクターは使用しないでください。


標準 (セクション 7.1.6.1 [dcl.type.cv]) から、作成されたオブジェクトを変更することconstは未定義の動作です。

ミュータブルと宣言されたクラス メンバー (7.1.1) を変更できることを除いて、その有効期間中 (3.8) に const オブジェクトを変更しようとすると、未定義の動作が発生します。

于 2012-01-20T04:12:37.103 に答える
4

述べたように、問題は間違っていると感じています。


構造上、 Astd::stringはメモリを所有します。既存のバッファへの単純な参照が必要な場合は、次のようなものを使用できますllvm::StringRef

class StringRef {
public:
  constexpr StringRef(char const* d, size_t s): data(d), size(s) {}

private:
  char const* data;
  size_t size;
};

もちろん、strlen他のすべてのC関数がそうではない constexprという残念なことがあります。これは標準の欠陥のように感じます(すべての数学関数について考えてください...)。


状態については、保存方法を理解していれば(少し)できます。ループは再帰と同等であることを覚えていますか?同様に、状態を引数としてヘルパー関数に渡すことで、状態を「保存」できます。

// potentially unsafe (non-limited)
constexpr int length(char const* c) {
  return *c == '\0' ? 0 : 1 + length(c+1);
}

// OR a safer version
constexpr int length_helper(char const* c, unsigned limit) {
  return *c == '\0' or limit <= 0 ? 0 : 1 + length_helper(c+1, limit-1);
}

constexpr int length256(char const* c) { return length_helper(c, 256); }

もちろん、この状態のこの形式はある程度制限されており(複雑な構造を使用することはできません)、それがの制限ですconstexpr。しかし、それはすでに大きな飛躍です。さらに進むということは、純度をさらに深くすることを意味します(これは、C ++ではほとんど不可能です)。

于 2012-01-20T08:37:03.287 に答える
2

標準に準拠したC++11実装で、constexprである引数に基づいて関数のオーバーロードを許可することは可能ですか、それとも標準を更新する必要がありますか?許可されていない場合、意図的に許可されていませんか?

標準で何かができると書かれていない場合、誰かにそれを許可することは非標準的な動作になります。したがって、それを許可したコンパイラは、言語拡張を実装することになります。

結局のところ、それは必ずしも悪いことではありません。ただし、C++11に準拠していません。

標準化委員会の意図を推測することしかできません。彼らは故意にそれを許可しなかったかもしれません、あるいはそれは見落としのようなものだったかもしれません。事実、標準はオーバーロードが許可されていないため、許可されていません。

于 2012-01-20T04:27:20.583 に答える