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:enum
をstd::string
にマップする関数があるとします。これを行う最も簡単な方法は、私のenum
が0
から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
引数を渡した場合、コンパイル時間はどのくらいでしたか。
引数ではなく、結果が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)中に変更しようとすると、未定義の動作が発生します。
この機能が欠落していることに同意します-私もそれが必要です。例:
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に減らしたかどうかを予測するのは難しい場合があります。時定数をコンパイルするかどうか。
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
私が試したいくつかのコンパイラでは、期待どおりに動作します。
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
_呼び出しに展開されます。
述べたように、問題は間違っていると感じています。
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++ではほとんど不可能です)。
標準準拠のC++ 11実装で、constexprである引数に基づいて関数のオーバーロードを許可することは可能ですか、それとも標準を更新する必要がありますか?許可されていない場合、意図的に許可されていませんか?
標準で何かできると書かれていない場合、誰かにそれを許可することは非標準的な動作になります。したがって、それを許可したコンパイラは、言語拡張を実装することになります。
結局のところ、それは必ずしも悪いことではありません。ただし、C++ 11に準拠していません。
標準委員会の意図を推測することしかできません。彼らは故意にそれを許可しなかったかもしれません、あるいはそれは何か見落としだったかもしれません。事実、標準はオーバーロードが許可されていないため、許可されていません。
アプローチ RichardSmithによる提案を使用して、特定の静的ストレージ変数が定数式であるかどうかを識別することができます。 _変換ルールの絞り込みに基づいています。
unsigned int
にconsexpr
non-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
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
{
}
}