web-dev-qa-db-ja.com

constexprのオーバーロード

関連: constexprを返す関数はコンパイルされません

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

したがって、私の質問はこれです:

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


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

static constexpr char const * []を作成してリターン時にstd::stringを作成するか(関数を呼び出すたびにstd::stringオブジェクトを作成するコストを支払う)、またはstatic std::string const []を作成することができます。そして、私が最初に関数を呼び出すときに、すべてのstd::stringコンストラクターのコストを支払って、調べた値を返します。より良い解決策は、コンパイル時にメモリにstd::stringを作成することであるように思われます(現在char const *で行われていることと同様)が、これを行う唯一の方法は、コンストラクターに次のことを警告することです。 constexpr引数があります。

std::stringコンストラクター以外の例の場合、constexprの要件を無視できる(したがって、非constexpr function)、より効率的な関数を作成できます。このスレッドを考えてみましょう: constexprの質問、なぜこれら2つの異なるプログラムがg ++でこのように異なる時間で実行されるのですか?

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

47
David Stone

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

const std::stringは、リテラルへのポインタが書き込まれないことを認識して、リテラルへのポインタを格納できます(const_castを使用してstd::stringからconstを削除する必要があり、それはすでに未定義の動作)。破棄中にバッファが解放されないようにするには、ブールフラグを格納する必要があります。

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


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

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

7
Ben Voigt

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

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に減らしたかどうかを予測するのは難しい場合があります。時定数をコンパイルするかどうか。

35
A Fog

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

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

これは、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++でもそれを検出する方法があります。次のトリックはJohannesSchaubによって発明されたと確信していますが、引用を見つけることができません。とても素敵で明確なトリック。

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

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

8
Öö Tiib

C++ 11には「constexproverloading」のようなものはありませんが、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_呼び出しに展開されます。

6
Lockal

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


std::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関数がnotconstexprであるという残念な点があります。 Thisは標準の欠陥のように感じます(すべての数学関数について考えてください...)。


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

// 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++ではほとんど不可能です)。

4
Matthieu M.

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

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

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

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

2
Nicol Bolas

アプローチ RichardSmithによる提案を使用して、特定の静的ストレージ変数が定数式であるかどうかを識別することができます。 _変換ルールの絞り込みに基づいています。

unsigned intconsexprnon-negativeintナローイングなしでを割り当てることができます。

unsigned int u {std::max(0, -3)}; // compiles, max is constexpr

ただし、変数を使用する場合、上記を実行することはできません。

int a = 3;
unsigned int u {std::max(0, a)}; // compilation error, narrowing int to unsigned int

特定のint referenceがconst式であるかどうかを識別するために、正のまたはを使用してunsigned intナローイングなしに割り当てることができるかどうかをテストできます。 _負の値。これは、コンパイル時に値がわかっているintで可能である必要があります。つまり、定数式と見なすことができます。

template<const int& p> std::true_type
    is_constexpr_impl(decltype((unsigned int){std::max(-p, p)}));
template<const int& p> std::false_type
    is_constexpr_impl(...);
template<const int& p> using is_constexpr =
    decltype(is_constexpr_impl<p>(0));

これで、マクロアプローチを使用して、実行時とコンパイル時にさまざまな実装を行うことができます。

int foo_runtime(int num) {
    return num;
}

constexpr int foo_compiletime(int num) {
      return num + 1;
}

#define foo(X) (is_constexpr<X>()?foo_compiletime(X):foo_runtime(X))

そして、前述のように、 const式のオーバーロードを模倣します

int main() {
    static int a = 3;
    static const int b = 42; // considered constexpr
    static const int c = foo_runtime(42); // not constexpr
    static constexpr int d = 4;
    static constexpr int e = -2;
    static int f = 0;
    static const int g = 0; // considered constexpr

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

上記は素晴らしいですが、静的ストレージ変数に制限されているため、あまり役に立ちません。ただし、constexprに基づくオーバーロードは発生します。


変換を狭めることに依存せずに、同じことを達成するための別のアプローチ、 can be

template<const int& p> std::true_type
    is_constexpr_impl(std::array<int, std::max(p, -p)>);
template<const int& p> std::false_type
    is_constexpr_impl(...);
template<const int& p> using is_constexpr = 
    decltype(is_constexpr_impl<p>(0));

上記のstd::arrayの使用は、単純なc-arrayの使用に置き換わります このアプローチではgccではうまく機能しません


または別の1つ-再び、ナローイングルールに依存せずに これも正常に機能します

template<const int& p, typename T = void>
struct is_constexpr: std::false_type {};

template<const int& p>
struct is_constexpr<p, std::void_t<int[std::max(p,-p)+1]>>: std::true_type {};

同じことを達成しようとする場合より単純なアプローチ

template<typename T>
struct is_constexpr: std::false_type {};

template<typename T>
struct is_constexpr<const T>: std::true_type {};

#define foo(X) (is_constexpr<decltype(X)>()?foo_compiletime(X):foo_runtime(X))

この行の目標は達成できません:

static const int c = foo_runtime(42); // const but not constexpr
0
Amir Kirsh

SFINAEを使用してコンパイル時のコンパイルを検出する別のオプション: http://coliru.stacked-crooked.com/a/f3a2c11bcccdb5bf

template<typename T>
auto f(const T&)
{
  return 1;
}

constexpr auto f(int)
{
  return 2;
}



////////////////////////////////////////////////////////////////////////
template<typename T, int=f(T{})>
constexpr bool is_f_constexpr_for(int) {return true;}

template<typename...>
constexpr bool is_f_constexpr_for(...) {return false;}



template<typename T>
auto g(const T& t)
{
  if constexpr (is_f_constexpr_for<T>(0))
  {

  }
  else
  {

  }
}
0
NN_