読者です 読者をやめる 読者になる 読者になる

とくにあぶなくないRiSKのブログ

危ないRiSKのブログだったかもしれない。本当はRiSKだけどググラビリティとか取得できるIDの都合でsscriskも使ったり。

括弧初期化リスト(braced-init-list)内では順番に評価される

通常の引数の評価順序

C言語でもC++言語でも関数呼出しのために渡される引数の評価順序は未規定となっています。

#include<stdio.h>

void print_sum(int a, int b, int c, int d){
    printf("\n%d\n", a + b + c + d);
}

int main(){
    print_sum(printf("%s", "hello"), printf("%c", ','), printf("%s", "world"), printf("%c", '!'));
}

この例では文字数である"12"がprint_sumによって出力される前に、どのような順番で文字列が出力されるのか予想できません。 "hello,world!"と表示されるかもしれませんし、"!world,hello"と表示されるかもしれませんし、",!worldhello"と表示されるかもしれないわけです。

[Wandbox]三へ( へ՞ਊ ՞)へ ハッハッ

引数の評価順序の例外

しかし、C++には評価順序が保証される例外があります。

Working Draft, Standard for Programming Language C++ (N3936)より引用。

構文

expression-list:
initializer-list
initializer-clause:
assignment-expression
braced-init-list
initializer-list:
initializer-clause ...opt
initializer-list , initializer-clause ...opt
braced-init-list:
{ initializer-list ,opt }
{ }

N3936 8.5.4/4

引用と訳。

Within the initializer-list of a braced-init-list, the initializer-clauses, including any that result from pack expansions (14.5.3), are evaluated in the order in which they appear. That is, every value computation and side effect associated with a given initializer-clause is sequenced before every value computation and side effect associated with any initializer-clause that follows it in the comma-separated list of the initializer-list.

括弧初期化リスト(braced-init-list)の初期化リスト(initializer-list)とパック展開(pack expansions)を含む初期化節(initializer-clauses)は、現れる順番に評価されます。 つまり、与えられた初期化節と関連するすべての値の計算と副作用は、次のコンマ区切りのリストのすべての値の計算と副作用の前におこります。

[ Note: This evaluation ordering holds regardless of the semantics of the initialization; for example, it applies when the elements of the initializer-list are interpreted as arguments of a constructor call, even though ordinarily there are no sequencing constraints on the arguments of a call. —end note ]

[注:この評価順序は初期化の意味論に関係なく決まります。 例えば、通常はコンストラクタ呼出しの引数は順番に評価されないですが、初期化リストの要素をコンストラクタ呼出しの引数として評価する時がそれに当てはまります。]

まとめ

通常は関数呼出しのために渡される引数の評価順序は未規定で予想できない。 順序を保証する必要がある時は括弧初期化リストを使う。

コンストラクタ呼出しの仕方で結果が変わ(りう)る例:

#include<stdio.h>

struct print_sum{
    print_sum(int a, int b, int c, int d){
        printf("\n%d\n", a + b + c + d);
    }
};

int main(){
    // 出力は予想できない
    print_sum(printf("%s", "hello"), printf("%c", ','), printf("%s", "world"), printf("%c", '!'));
    putchar('\n');
    // 期待通りの出力
    print_sum{printf("%s", "hello"), printf("%c", ','), printf("%s", "world"), printf("%c", '!')};
}

実行結果(例)

!world,hello
12

hello,world!
12

[Wandbox]三へ( へ՞ਊ ՞)へ ハッハッ