web-dev-qa-db-ja.com

コンパイル時にC文字列の長さを計算します。これは本当にconstexprですか?

コンパイル時に文字列リテラルの長さを計算しようとしています。そのためには、次のコードを使用しています。

#include <cstdio>

int constexpr length(const char* str)
{
    return *str ? 1 + length(str + 1) : 0;
}

int main()
{
    printf("%d %d", length("abcd"), length("abcdefgh"));
}

すべてが期待どおりに機能し、プログラムは4と8を出力します。clangによって生成されたアセンブリコードは、結果がコンパイル時に計算されることを示しています。

0x100000f5e:  leaq   0x35(%rip), %rdi          ; "%d %d"
0x100000f65:  movl   $0x4, %esi
0x100000f6a:  movl   $0x8, %edx
0x100000f6f:  xorl   %eax, %eax
0x100000f71:  callq  0x100000f7a               ; symbol stub for: printf

私の質問:length関数がコンパイル時に評価されることが標準によって保証されていますか?

これが当てはまる場合、コンパイル時の文字列リテラル計算のドアが開かれました...たとえば、コンパイル時などにハッシュを計算できます...

84
Felics

定数式はコンパイル時に評価されることが保証されていません。 ドラフトC++標準 セクション5.19これを言っている定数式

[...]> [注:定数式は翻訳中に評価できます。

コンパイル時に評価されるように、結果をconstexpr変数に割り当てることができます。これは、 Bjarne StroustrupのC++ 11リファレンスエンファシス鉱山):

コンパイル時に式を評価できることに加えて、コンパイル時に評価される式を必要とすることができます。変数定義の前のconstexprは、それを行います(およびconstを意味します):

例えば:

constexpr int len1 = length("abcd") ;

Bjarne Stroustrupは、この isocppブログエントリ でコンパイル時の評価を保証できる時期の概要を示し、次のように述べています。

[...]正しい答えは-ハーブが述べているように-定数式として使用されない限り、標準に従ってconstexpr関数はコンパイラー時または実行時に評価され、その場合はコンパイル時に評価されなければならないということです-時間。コンパイル時の評価を保証するために、定数式が必要な場所(配列バインドまたはケースラベルなど)で使用するか、それを使用してconstexprを初期化する必要があります。自尊心のあるコンパイラが、私が最初に言ったことを実行するための最適化の機会を逃さないことを願っています。

そのため、コンパイル時に評価する必要がある2つのケースの概要を説明します。

  1. 定数式が必要な場合に使用します。これは、ドラフト標準の句shall be ... converted constant expression または shall be ... constant expressionが使用されます(配列バインドなど)。
  2. 上記で説明したように、constexprを初期化するために使用します。
68
Shafik Yaghmour

constexpr関数の呼び出しがコア定数式になるのか、単に最適化されているだけなのかを見つけるのは本当に簡単です。

定数式が必要なコンテキストで使用します。

int main()
{
    constexpr int test_const = length("abcd");
    std::array<char,length("abcdefgh")> test_const2;
}
25
Ben Voigt

最近のコンパイラ(gcc-4.xなど)は、通常 intrinsic function として定義されているため、コンパイル時に文字列リテラルに対してstrlenを実行します。最適化が有効になっていない場合。結果はコンパイル時定数ではありませんが。

例えば。:

printf("%zu\n", strlen("abc"));

結果:

movl    $3, %esi    # strlen("abc")
movl    $.LC0, %edi # "%zu\n"
movl    $0, %eax
call    printf
16

再帰的ではなくコンパイル時に文字列の長さを計算する別の関数を提案させてください。

template< size_t N >
constexpr size_t length( char const (&)[N] )
{
  return N-1;
}

これをご覧ください ideoneのサンプルコード

12
user2436830

constexpr関数がコンパイル時に評価されるという保証はありませんが、妥当なコンパイラーは適切な最適化レベルが有効になっているときにそれを行います。一方、テンプレートパラメータmustはコンパイル時に評価されます。

次のトリックを使用して、コンパイル時に評価を強制しました。残念ながら、整数値でのみ機能します(つまり、浮動小数点値では機能しません)。

template<typename T, T V>
struct static_eval
{
  static constexpr T value = V;
};

今、あなたが書くなら

if (static_eval<int, length("hello, world")>::value > 7) { ... }

ifステートメントは、実行時のオーバーヘッドのないコンパイル時定数であることを確認できます。

6
5gon12eder

一般化された定数式 に関するウィキペディアのエントリからの短い説明:

関数でconstexprを使用すると、その関数で実行できる機能にいくつかの制限が課せられます。まず、関数は非void戻り値型を持っている必要があります。第二に、関数本体は変数を宣言したり、新しい型を定義したりできません。第三に、本文には宣言、nullステートメント、および単一のreturnステートメントのみを含めることができます。引数の置換後、returnステートメントの式が定数式を生成するような引数値が存在する必要があります。

関数定義の前にconstexprキーワードを指定すると、これらの制限が満たされているかどうかを確認するようコンパイラーに指示します。 yesの場合、定数を指定して関数が呼び出されると、戻り値は定数であることが保証されるため、定数式が必要な場所であればどこでも使用できます。

1
kaedinger