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


94

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

#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関数がコンパイル時に評価されることは規格によって保証されていますか?

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


3
パラメータが定数式である限り、そうでなければなりません。
クリス

1
@chris定数式を必要としないコンテキストで使用する場合、定数式になる可能性があるものをコンパイル時に評価する必要があるという保証はありますか?
TC

12
ところで、<cstdio>呼び出しとその後の呼び出し::printfは、移植性がありません。標準は<cstdio>を提供することのみを要求しますstd::printf
Ben Voigt 2014

1
@BenVoigt OK、指摘してくれてありがとう:)最初はstd :: coutを使用しましたが、実際の値を見つけるために生成されたコードはかなり大きくなりました:)
Mircea Ispas

3
@Felicsは、私がよく使うgodboltを最適化を扱うの質問に答えると、使用している場合printfに対処するため大幅に少ないコードにつながることができます。
Shafik Yaghmour 2014

回答:


76

定数式は、コンパイル時に評価されることが保証されていません。ただし、C ++標準ドラフトセクションの5.19 定数式からの非規範的な引用しかありません。

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

結果をconstexpr変数に割り当てて、コンパイル時に確実に評価されるようにすることができます。これは、Bjarne StroustrupのC ++ 11リファレンスで確認できます(強調は次のとおりです)。

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

例えば:

constexpr int len1 = length("abcd") ;

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

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

したがって、これはコンパイル時に評価する必要がある2つのケースの概要です。

  1. 定数式が必要な場合に使用します。これは、ドラフト標準のフレーズshall be ... converted constant expressionまたはshall be ... constant expressionが使用される場所(配列の境界など)のどこかにあるようです。
  2. constexpr上記で概説したように、それを使用して初期化します。

4
とは言っても、原則的にコンパイラーはと内部にあるか、リンケージがないオブジェクトを見る権利がconstexpr int x = 5;あり、コンパイル時に値を必要としないことを確認し(テンプレートパラメーターとして使用されていない場合など)、実際に出力します1と4の加算演算の5つの即値を使用して実行時に初期値を計算するコード。より現実的な例:コンパイラは再帰制限に達し、実行時まで計算を延期する可能性があります。コンパイラーに実際に値を使用することを強制する何かを行わない限り、「コンパイル時に評価されることが保証される」はQOIの問題です。
スティーブジェソップ2014

@SteveJessop Bjarneは、翻訳時に評価される定数式の手段として使用されるドラフト標準で見つけることができる類似体がない概念を使用しているようです。したがって、標準は彼の言っていることを明示的に述べていないように思われるので、私はあなたに同意する傾向があります。BjarneとHerbの両方がこれに同意しているように見えますが、これは仕様が不十分であることを示している可能性があります。
Shafik Yaghmour 2014

2
私はどちらも、「自己尊重コンパイラー」のみを検討していると思います。これは、標準に準拠しているが意図的に妨害するコンパイラーとは対照的です。それは標準が実際に何を保証するかについて推論する手段として有用であり、それ以外はそれほどではありません;-)
Steve Jessop

3
@SteveJessop悪名高い(そして残念ながら存在しない)Hell ++のような意図的に邪魔なコンパイラ。このようなことは、実際に適合性/移植性のテストに最適です。
Angewは、2014

as-ifルールでは、値を見かけ上のコンパイル時定数として使用するだけでは不十分です。コンパイラーは、ソースのコピーを出荷して実行時に再コンパイルするか、計算を行って型を判別します。変数、または無意味にconstexpr計算をまったくの悪から再実行します。特定のソースのラインでキャラクターごとに1秒待つか、特定のソースのラインを使用してチェスの位置をシードし、両側をプレイして勝者を決定することも自由です。
Yakk-Adam Nevraumont 2014

27

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

定数式が必要なコンテキストで使用してください。

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

4
... -pedanticgccを使用する場合は、でコンパイルします。そうしないと、あなたは何の警告やエラーを取得していない
BЈовић

@BЈовићまたは、テンプレート引数など、GCCが潜在的に邪魔になる拡張機能がないコンテキストで使用します。
AngewはSO

Wouldnは\トン列挙ハックは、より信頼性が高いですか?などenum { Whatever = length("str") }
シャープトゥース'17

18
言及にstatic_assert(length("str") == 3, "");
値するの

8
constexpr auto test = /*...*/;おそらく最も一般的で簡単です。
TC

19

ただ注意してください。最近のコンパイラー(gcc-4.xなど)はstrlen、通常、組み込み関数として定義されているため、コンパイル時に文字列リテラルを処理します。最適化が有効になっていません。結果はコンパイル時定数ではありませんが。

例えば:

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

結果:

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

これstrlenは、組み込み関数であるため機能することに注意してください。これを使用-fno-builtinsすると、実行時に呼び出しに戻る場合、ライブ
Shafik Yaghmour

strlenconstexprで、私のため-fno-nonansi-builtins(のように思える-fno-builtins++もはやグラムには存在しません)。私は「constexpr」と言います。これはtemplate<int> void foo();、これとfoo<strlen("hi")>(); g ++-4.8.4を
アーロン・マクデイド2015

18

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

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

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


4
'\ 0'が埋め込まれているため、strlenと等しくない場合があります。strlen( "hi \ 0there")!= length( "hi \ 0there")
unkulunkulu

これは正しい方法です。これは、Effective Modern C ++の例です(私が正しく思い出した場合)。ただし、完全にconstexprである素敵な文字列クラスがあります。この回答を参照してください。ScottSchurrのstr_const、おそらくこれはより便利です(そしてCスタイルではありません)。
QuantumKarl 2017年

@MikeWeir Ops、それは奇妙です。ここにさまざまなリンクがあります:質問へのリンク、紙へのリンク、git上のソースへのリンク
QuantumKarl

今すぐやる:char temp[256]; sprintf(temp, "%u", 2); if(1 != length(temp)) printf("Your solution doesn't work"); ideone.com/IfKUHV
Pablo Ariel

7

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

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

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

今、あなたが書くなら

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

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


8
または単にstd :: integral_constant <int、length(...)> :: valueを使用します
Mircea Ispas

1
とにかくコンパイル時lenconstexpr手段であるlengthことを評価する必要があるため、この例は少し無意味な使い方です。
クリス

@chris私はそれ私のコンパイラにあることを観察しました、それそうである必要があることを知りませんでした。
5gon12eder 2014

わかりました。他の回答の大部分によれば、そうする必要があるため、この例を無意味なものに修正しました。実際、if私が最初にこのトリックを使用したのは、- 条件(コンパイラがデッドコードの除去を行うことが不可欠である)でした。
5gon12eder 2014

1

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

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

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


これらの条件は、戻り値が一定であることを保証しません。たとえば、関数は他の引数値で呼び出される場合があります。
Ben Voigt 2014

そう、@ BenVoigt。定数式で呼ばれるように編集しました。
kaedinger 2014
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.