yohhoyの日記

技術的メモをしていきたい日記

i += 1 + ++i; の処理結果(Go/Rust編)

異なるプログラミング言語における式i += 1 + ++i処理結果の違いについて。番外編。

// Go
var i = 0
i += 1 + ++i  // ??
// Rust
let mut i = 0;
i += 1 + ++i;  // ??

まとめ:

Go

プログラミング言語Goでは設計判断として前置インクリメント++iを提供しない。また後置インクリメントi++は提供されるが、式(expression)ではなく文(statement)として提供される(→id:yohhoy:20200131)。Go言語FAQより引用。

Why are ++ and -- statements and not expressions? And why postfix, not prefix?
Without pointer arithmetic, the convenience value of pre- and postfix increment operators drops. By removing them from the expression hierarchy altogether, expression syntax is simplified and the messy issues around order of evaluation of ++ and -- (consider f(i++) and p[i] = q[++i]) are eliminated as well. The simplification is significant. As for postfix vs. prefix, either would work fine but the postfix version is more traditional; insistence on prefix arose with the STL, a library for a language whose name contains, ironically, a postfix increment.

https://go.dev/doc/faq#inc_dec

Go言語では代入(=)や代入演算(+=など)は文(statement)として規定され*1、難解な式記述を許さない言語仕様となっている。Simplicity.*2

{ // Java/JavaScript/C#的な動作
  var i = 0;
  i = i + 1 + (i + 1)
  //          ^^^^^^^ ++iの代用
  // 変数 i は 値2
}
{ // C/C++的な動作
  var i = 0
  i++  // ++iの代用
  i += 1 + i
  //       ^ 項++i相当
  // 変数 i は 値3
}

Rust

プログラミング言語Rustでは設計判断としてインクリメント演算子(++)を提供しない*3。Rust言語Frequently Asked Questionsより引用。

Why doesn't Rust have increment and decrement operators?
Preincrement and postincrement (and the decrement equivalents), while convenient, are also fairly complex. They require knowledge of evaluation order, and often lead to subtle bugs and undefined behavior in C and C++. x = x + 1 or x += 1 is only slightly longer, but unambiguous.

GitHub - dtolnay/rust-faq: Frequently Asked Questions · The Rust Programming Language

Rust言語において前置インクリメント++iと等価な式は{ i += 1; i }と記述できる*4。複合代入演算子(+=など)のオペランド評価順はオペランド型に依存して評価順序が変化することに注意*5。両オペランドともプリミティブ型の場合は「右辺→左辺」順で評価が行われる。Here Be Dragons/Hic Sunt Dracones🐉

{ // Java/JavaScript/C#的な動作
  let mut i = 0;
  i = i + 1 + { i += 1; i };
  assert!(i == 2);
  // 部分式の評価順:
  //  i = i + 1 + { i += 1; i };
  // (6) (1) (2)   (4)  (3)(5)
}
{ // C/C++的な動作
  let mut i = 0;
  i += 1 + { i += 1; i };
  assert!(i == 3);
  // 部分式の評価順:
  //  i += 1 + { i += 1; i };
  // (5)  (1)   (3)  (2)(4)
}

Rust言語リファレンスより一部引用(下線部は強調)。

Evaluation order of operands
The following list of expressions all evaluate their operands the same way, as described after the list. Other expressions either don’t take operands or evaluate them conditionally as described on their respective pages.

  • (snip)
  • Arithmetic and logical binary operators
  • (snip)

The operands of these expressions are evaluated prior to applying the effects of the expression. Expressions taking multiple operands are evaluated left to right as written in the source code.

Note: Which subexpressions are the operands of an expression is determined by expression precedence as per the previous section.

For example, the two next method calls will always be called in the same order:

let mut one_two = vec![1, 2].into_iter();
assert_eq!(
    (1, 2),
    (one_two.next().unwrap(), one_two.next().unwrap())
);

Note: Since this is applied recursively, these expressions are also evaluated from innermost to outermost, ignoring siblings until there are no inner subexpressions.

https://doc.rust-lang.org/reference/expressions.html#evaluation-order-of-operands

Compound assignment expressions
(snip)
Evaluation of compound assignment expressions depends on the types of the operators.

If both types are primitives, then the modifying operand will be evaluated first followed by the assigned operand. It will then set the value of the assigned operand's place to the value of performing the operation of the operator with the values of the assigned operand and modifying operand.

Note: This is different than other expressions in that the right operand is evaluated before the left one.

Otherwise, this expression is syntactic sugar for calling the function of the overloading compound assignment trait of the operator (see the table earlier in this chapter). A mutable borrow of the assigned operand is automatically taken.

(snip)

Like assignment expressions, compound assignment expressions always produce the unit value.

⚠️Warning: The evaluation order of operands swaps depending on the types of the operands: with primitive types the right-hand side will get evaluated first, while with non-primitive types the left-hand side will get evaluated first. Try not to write code that depends on the evaluation order of operands in compound assignment expressions. See this test for an example of using this dependency.

https://doc.rust-lang.org/reference/expressions/operator-expr.html#compound-assignment-expressions

関連URL

*1:https://go.dev/ref/spec#Assignment

*2:https://go.dev/talks/2015/simplicity-is-complicated.slide#4

*3:当然ながら、Rust言語ではデクリメント演算子 -- も提供されない。

*4:Rust言語では加算代入 i += 1 の評価結果はユニット値(unit value)となり、0 + (i += 1) のような式は型不整合によるコンパイルエラーとなる。

*5:代入演算子オーバーロード機能を提供するC++にも同様のオペランド評価順問題があり、C++17以降では「ユーザ型向けにオーバーロードされていても、プリミティブ型に対するオペランド評価順と統一する」規則を追加導入している。

i += 1 + ++i; の処理結果(C/C++編)

異なるプログラミング言語における式i += 1 + ++i処理結果の違いについてメモ。

警告:1つの式内で同一変数を複数回更新する技巧的なコード記述は避けてください。DO NOT WRITE THIS ON PRODUCTION CODE.

sequenced-before関係ルールを覚えてまで際どいコードを書くほど人生は長くないはず。

i = i++ + 1;の評価順規定 - yohhoyの日記
int i = 0; // Java,C#,C++,C
// let i = 0; // JavaScript

i += 1 + ++i;
// 変数 i の値は?

まとめ:整数型の変数iが値0で初期化さているとき、下記プログラミング言語における処理結果は次の通り。

複合代入演算子+=の左辺変数i値を “どのタイミングで読み取るか” により結果が異なる。C言語(C23現在)とC++14までは未定義動作(undefined behavior)となるが、C++17以降では順序規定の追加によって結果が保証されるようになる。こんなコードはダメ。ゼッタイ。

C90/99

式文末尾が副作用完了点(sequence point)と規定され、そこまでの間に複合代入(i += ...)および前置インクリメント(++i)により同一変数iに対する更新操作(modify)が複数回生じるため、未定義動作(undefined behavior)を引き起こす。C++98/03動作と同じ。

C99 6.5/p2, 6.5.16/p4, 6.8/p4より一部引用(下線部は強調)。

2 Between the previous and next sequence point an object shall have its stored value modified at most once by the evaluation of an expression. Furthermore, the prior value shall be read only to determine the value to be stored.

4 The order of evaluation of the operands is unspecified. If an attempt is made to modify the result of an assignment operator or to access it after the next sequence point, the behavior is undefined.

4 A full expression is an expression that is not part of another expression or of a declarator. Each of the following is a full expression: an initializer; the expression in an expression statement; (snip) The end of a full expression is a sequence point.

C11/17/23

複合代入演算子+=の左オペランド(i)における値の計算(value computation)と、右オペランドに含まれる前置インクリメント(++i)の副作用(side effect)が unsequenced 関係にあるため、式は未定義動作(undefined behavior)を引き起こす。C++11/14動作と同じ。

C23 6.5.1/p2-3、6.5.17.1/p3より引用(下線部は強調)。*1

2 If a side effect on a scalar object is unsequenced relative to either a different side effect on the same scalar object or a value computation using the value of the same scalar object, the behavior is undefined. If there are multiple allowable orderings of the subexpressions of an expression, the behavior is undefined if such an unsequenced side effect occurs in any of the orderings.
3 The grouping of operators and operands is indicated by the syntax. Except as specified later, side effects and value computations of subexpressions are unsequenced.

3 An assignment operator stores a value in the object designated by the left operand. An assignment expression has the value of the left operand after the assignment, but is not an lvalue. The type of an assignment expression is the type the left operand would have after lvalue conversion. The side effect of updating the stored value of the left operand is sequenced after the value computations of the left and right operands. The evaluations of the operands are unsequenced.

C++98/03

式文末尾が副作用完了点(sequence point)と規定され、そこまでの間に複合代入(i += ...)および前置インクリメント(++i)により同一変数iに対する更新操作(modify)が複数回生じるため、未定義動作(undefined behavior)を引き起こす。C90/99動作と同じ。

C++03+CWG351 1.9/p12, p16, 5/p4より引用(下線部は強調)。

12 A full-expression is an expression that is not a subexpression of another expression. If a language construct is defined to produce an implicit call of a function, a use of the language construct is considered to be an expression for the purposes of this definition.

16 There is a sequence point at the completion of evaluation of each full-expression.

4 Except where noted, the order of evaluation of operands of individual operators and subexpressions of individual expressions, and the order in which side effects take place, is unspecified. Between the previous and next sequence point a scalar object shall have its stored value modified at most once by the evaluation of an expression. Furthermore, the prior value shall be accessed only to determine the value to be stored. The requirements of this paragraph shall be met for each allowable ordering of the subexpressions of a full expression; otherwise the behavior is undefined. [Example:

i = v[i++];       // the behavior is unspecified
i = 7, i++, i++;  // i becomes 9

i = ++i + 1;      // the behavior is undefined
i = i + 1;        // the value of i is incremented

-- end example]

C++11/14

複合代入演算子+=の左オペランド(i)における値の計算(value computation)と、右オペランドに含まれる前置インクリメント(++i)の副作用(side effect)が unsequenced 関係にあるため、式は未定義動作(undefined behavior)を引き起こす。C11/17/23動作と同じ。

C++14 1.9/p15, 5.17/p1より一部引用(下線部は強調)。

15 Except where noted, evaluations of operands of individual operators and of subexpressions of individual expressions are unsequenced. [Note: In an expression that is evaluated more than once during the execution of a program, unsequenced and indeterminately sequenced evaluations of its subexpressions need not be performed consistently in different evaluations. -- end note] The value computations of the operands of an operator are sequenced before the value computation of the result of the operator. If a side effect on a scalar object is unsequenced relative to either another side effect on the same scalar object or a value computation using the value of the same scalar object, and they are not potentially concurrent (1.10), the behavior is undefined. (snip)

1 The assignment operator (=) and the compound assignment operators all group right-to-left. All require a modifiable lvalue as their left operand and return an lvalue referring to the left operand. The result in all cases is a bit-field if the left operand is a bit-field. In all cases, the assignment is sequenced after the value computation of the right and left operands, and before the value computation of the assignment expression. With respect to an indeterminately-sequenced function call, the operation of a compound assignment is a single evaluation. (snip)

C++17/20/23

C++11/14の規定に加えて、複合代入演算子+=の右オペランド(1 + ++i)は左オペランド(i)より前に順序付けられる(sequenced before)ため、下記順序で評価が行われ変数iは値3となる。

  1. 複合代入演算子+=右辺(1 + ++i)を評価し、値2をえる。
    1. 加算演算子+左辺(1)の値を計算(value computation)し、値1をえる。手順1.2とは順不同。
    2. 加算演算子+右辺(++i)を評価し、値1をえる。手順1.1とは順不同。
      1. 前置インクリメント++オペランド(i)の値を計算(value computation)し、値0をえる。
      2. 前置インクリメントの値を計算(value computation)し、手順1.2.1評価結果から0 + 1→値1をえる。
      3. 前置インクリメントの副作用(side effect)として、変数iに値1を代入する。
    3. 加算の値の計算(value computation)として、手順1.1〜1.2評価結果から1 + 1→値2をえる。
  2. 複合代入演算子+=左辺(i)の値を計算(value computation)し、値1をえる。
  3. 複合代入の値の計算(value computation)として、手順1〜2評価結果から1 + 2→値3をえる。
  4. 複合代入の副作用(side effect)として、変数iに値3を代入する。

C++23 7.6.19/p1より一部引用(下線部は強調)。

1 The assignment operator (=) and the compound assignment operators all group right-to-left. All require a modifiable lvalue as their left operand; their result is an lvalue of the type of the left operand, referring to the left operand. The result in all cases is a bit-field if the left operand is a bit-field. In all cases, the assignment is sequenced after the value computation of the right and left operands, and before the value computation of the assignment expression. The right operand is sequenced before the left operand. With respect to an indeterminately-sequenced function call, the operation of a compound assignment is a single evaluation. (snip)

関連URL

*1:C23 5.1.2.4/p2: "Evaluation of an expression in general includes both value computations and initiation of side effects."

i += 1 + ++i; の処理結果(Java/JavaScript/C#編)

異なるプログラミング言語における式i += 1 + ++i処理結果の違いについてメモ。*1

警告:1つの式内で同一変数を複数回更新する技巧的なコード記述は避けてください。こんな式を書くもんじゃねぇよ言語仕様書の紙束で張っ倒すぞ(#ノ゚Д゚)ㇸ📘 ...取り乱しました。

int i = 0; // Java,C#,C++,C
// let i = 0; // JavaScript

i += 1 + ++i;
// 変数 i の値は?

まとめ:整数型の変数iが値0で初期化さているとき、下記プログラミング言語における処理結果は次の通り。

複合代入演算子+=の左辺変数i値を “どのタイミングで読み取るか” により結果が異なる。JavaJavaScriptC#ではi = i + (1 + ++i)相当と解釈され、いずれも値2が言語仕様により保証された結果である。*2

Java

i += 1 + ++iは下記順序で評価され、最終的に変数iは値2となる。

  1. 複合代入演算子+=左辺(変数i)の値0を保存しておく(★L)。
  2. 複合代入演算子+=右辺(1 + ++i)を評価し、値2をえる(★R)。
    1. 加算演算子+左辺(1)を評価し、値1をえる。
    2. 加算演算子+右辺(++i)を評価し、変数iの値を01に更新したのち、値1をえる。
    3. 部分式(1 + ++i)の評価結果として、1 + 1より値2をえる。
  3. 複合代入演算子+=の計算プロセスとして、手順1:保存した左辺の値(★L)と手順2:右辺の評価結果(★R)に対して加算操作(+)を適用し、値2をえる。
  4. 手順3の評価結果2を、複合代入演算子+=左辺(変数i)に代入する。

複合代入演算子の評価プロセスについて、Java Language Specification(JLS) Java SE 23 Edition, 15.26.2より一部引用(下線部は強調)。*3

A compound assignment expression of the form E1 op= E2 is equivalent to E1 = (T) ((E1) op (E2)), where T is the type of E1, except that E1 is evaluated only once.
(snip)
At run time, the expression is evaluated in one of two ways.

If the left-hand operand expression is not an array access expression, then:

  • First, the left-hand operand is evaluated to produce a variable. If this evaluation completes abruptly, then the assignment expression completes abruptly for the same reason; the right-hand operand is not evaluated and no assignment occurs.
  • Otherwise, the value of the left-hand operand is saved and then the right-hand operand is evaluated. If this evaluation completes abruptly, then the assignment expression completes abruptly for the same reason and no assignment occurs.
  • Otherwise, the saved value of the left-hand variable and the value of the right-hand operand are used to perform the binary operation indicated by the compound assignment operator. If this operation completes abruptly, then the assignment expression completes abruptly for the same reason and no assignment occurs.
  • Otherwise, the result of the binary operation is converted to the type of the left-hand variable, and the result of the conversion is stored into the variable.
JLS 23, 15.26.2 Compound Assignment Operators

JavaScript

i += 1 + ++iは下記順序で評価され、最終的に変数iは値2となる。

  1. 複合代入演算子+=左辺(変数i)の値0を保存しておく(lval)。
  2. 複合代入演算子+=右辺(1 + ++i)を評価し、値2をえる(rval)。
    1. 加算演算子+左辺(1)を評価し、値1をえる。
    2. 加算演算子+右辺(++i)を評価し、変数iの値を01に更新したのち、値1をえる。
    3. 部分式(1 + ++i)の評価結果として、1 + 1より値2をえる。
  3. 複合代入演算子+=の計算プロセスとして、保存した左辺の値(lval)と右辺の評価結果(rval)に対して加算操作(+)を適用し、値2をえる(r)。
  4. 手順3の評価結果2(r)を、複合代入演算子+=左辺(変数i; lref)に代入する。

複合代入演算子の評価プロセスについて、ECMAScript 2024 Language Specification 15th edition, 13.15.2より一部引用。*4

AssignmentExpression : LeftHandSideExpression AssignmentOperator AssignmentExpression

  1. Let lref be ? Evaluation of LeftHandSideExpression.
  2. Let lval be ? GetValue(lref).
  3. Let rref be ? Evaluation of AssignmentExpression.
  4. Let rval be ? GetValue(rref).
  5. Let assignmentOpText be the source text matched by AssignmentOperator.
  6. Let opText be the sequence of Unicode code points associated with assignmentOpText in the following table:
    • assignmentOpText += / opText +
    • (snip)
  7. Let r be ? ApplyStringOrNumericBinaryOperator(lval, opText, rval).
  8. Perform ? PutValue(lref, r).
  9. Return r.
ECMAScript 2024 Language Specification, 13.15.2 Runtime Semantics: Evaluation

C#

i += 1 + ++iは式i = i + (1 + ++i)として下記順序で評価され、最終的に変数iは値2となる。

  1. 複合代入演算子+=左辺(変数i)を評価し、値0をえる。
  2. 加算演算子+左辺(1)を評価し、値1をえる。
  3. 加算演算子+右辺(++i)を評価し、変数iの値を01に更新したのち、値1をえる。
  4. 手順1〜3評価結果から0 + (1 + 1)→値2を算出し、複合代入演算子+=左辺(変数i)に代入する。

演算子オペランドの評価順序および複合代入演算子の評価プロセスについて、ECMA-334 6th edition*5, 11.4.1, 11.18.2, 11.18.3より一部引用。

11.4 Operators
11.4.1 General
Expressions are constructed from operands and operators. The operators of an expression indicate which operations to apply to the operands.

(snip)

The order of evaluation of operators in an expression is determined by the precedence and associativity of the operators (§11.4.2).

Operands in an expression are evaluated from left to right.

Example: In F(i) + G(i++) * H(i), method F is called using the old value of i, then method G is called with the old value of i, and, finally, method H is called with the new value of i. This is separate from and unrelated to operator precedence. end example

11.18.2 Simple assignment
The run-time processing of a simple assignment of the form x = y consists of the following steps:

  • If x is classified as a variable:
    • x is evaluated to produce the variable.
    • y is evaluated and, if required, converted to the type of x through an implicit conversion (§10.2).
    • If the variable given by x is an array element of a reference_type, (snip)
    • The value resulting from the evaluation and conversion of y is stored into the location given by the evaluation of x.
  • (snip)

11.18.3 Compound assignment

An operation of the form x «op»= y is processed by applying binary operator overload resolution (§11.4.5) as if the operation was written x «op» y. Then,

  • If the return type of the selected operator is implicitly convertible to the type of x, the operation is evaluated as x = x «op» y, except that x is evaluated only once.
  • (snip)

The term "evaluated only once" means that in the evaluation of x «op» y, the results of any constituent expressions of x are temporarily saved and then reused when performing the assignment to x.

関連URL

*1:謝辞:本記事は https://twitter.com/YojoPaisen/status/1859465566324654494 および https://twitter.com/it_muitenai/status/1859163754644083193 経由で頂いた情報をベースにしています。

*2:JavaおよびJavaScript(ECMAScript)言語仕様の解釈には曖昧さが無い。一方でC#言語の仕様記述は曖昧さが残るため、本文中の仕様解釈に多少の疑念がないわけでもない。これ以外の解釈とする必然性もないし、たぶん合ってるんじゃない?

*3:JLS 同セクションの Example 15.26.2-2. Value Of Left-Hand Side Of Compound Assignment Is Saved Before Evaluation Of Right-Hand Side にて本記事との類似ケースが例示説明されている。

*4:ECMAScript言語仕様のRuntime Semantics記述に頻出する ? 記号は、仮想操作ReturnIfAbrupt適用を表す略記用プレフィクス(prefix)である。本記事においては例外を扱わないため、単に無視してもよい。詳細は§5.2.3.4 ReturnIfAbrupt Shorthands参照のこと。

*5:https://ecma-international.org/wp-content/uploads/ECMA-334_6th_edition_june_2022.pdf

std::monostate as a Unit Type

C++標準ライブラリで提供されるstd::monostateは、単一値しか持たない ユニット型(Unit type) としても利用できる。*1

2024-11-29追記:C++2c(C++26)に向けて提案文書(PDF)P0472R3が採択され、ヘッダ<utility>にも汎用部品としてstd::monostateが追加される。*2

std::monostateはデフォルト構築可能でコピー操作や比較演算全般をサポートする正則な(regular)型であり*3、クラステンプレート特殊化を必要とするvoid型よりも自然に「情報を持たない」ことを表現できる。*4

#include <concepts>
#include <variant>

static_assert(std::regular<std::monostate>);
static_assert(not std::regular<void>);  // voidは非正則

関連URL

*1:C++17以降のヘッダ<variant>中にて定義され、本来は std::variant<std::monostate, Types...> のように空(empty)ステートを表現するためのクラス。

*2:https://github.com/cplusplus/papers/issues/1993

*3:ある型が正則(regular)となるには、==, != 演算子による等値比較のみ可能であればよい。std::monostate は等値比較以外にも大小比較やハッシュ演算をサポートする。

*4:例:std::future<T> は結果型を持たない std::future<void> 特殊化を提供する。

16進文字から数値への変換: ch - 'a' + 10

プログラミング言語Cの次期仕様C2yでは、16進数表記で用いられるアルファベット文字範囲'a''f'および'A''F'について文字コードの連続性保証が明文化される。

この文字コード範囲での連続性保証は、ラテンアルファベット26文字 A~Z / a~z が連続配置されるASCII互換文字コードUnicodeだけでなく、EBCDIC文字コードにおいても成立する*1

int ch = /*...*/;
assert(isxdigit(ch));  // chは任意の16進文字

int n;
if ('0' <= ch && ch <= '9') {
  n = ch - '0';  // 0...9
} else if ('a' <= ch && ch <= 'f') {
  n = ch - 'a' + 10;  // a...f
} else {
  n = ch - 'A' + 10;  // A...F
}

C2y WD N3301 5.3.1/p3-5, Annex M.2より一部引用。

3 (snip) In both the source and execution basic character sets, the value of each character after 0 in the preceding list of decimal digits shall be one greater than the value of the previous. (snip)
4 The value of each character after a, up to and including f, in the prior specified list of lowercase letters shall be one greater than the value of the previous.
5 The value of each character after A, up to and including F, in the prior specified list of uppercase letters, shall be one greater than the value of the previous.

Uppercase characters "A" through "F" ('A' through 'F') and lowercase characters "a" through "f" ('a' through 'f'), colloquially known as the "hexidecimal digits", are now guaranteed to be sequential.

関連URL

*1:EBCDICではアルファベット文字コードの割当が A~I / J~R / S~Z の3ブロックに分かれている。数値文字コード 0~9 は連続配置される。

std::array<T, 0>

C++標準ライブラリの固定長配列型std::array<T, N>では、要素数ゼロN == 0が明示的に許容される。一方で、C++言語組込の配列型においては要素数ゼロが許容されない。*1

C++11 8.3.4/p1, 23.3.2.8より一部引用。

In a declaration T D where D has the form

  D1 [ constant-expressionopt] attribute-specifier-seqopt

and the type of the identifier in the declaration T D1 is "derived-declarator-type-list T", then the type of the identifier of D is an array type; if the type of the identifier of D contains the auto type-specifier, the program is ill-formed. T is called the array element type; this type shall not be a reference type, the (possibly cv-qualified) type void, a function type or an abstract class type. If the constant-expression (5.19) is present, it shall be an integral constant expression and its value shall be greater than zero. The constant expression specifies the bound of (number of elements in) the array. If the value of the constant expression is N, the array has N elements numbered 0 to N-1, and the type of the identifier of D is "derived-declarator-type-list array of N T". (snip)

1 array shall provide support for the special case N == 0.
2 In the case that N == 0, begin() == end() == unique value. The return value of data() is unspecified.
3 The effect of calling front() or back() for a zero-sized array in undefined.
4 Member function swap() shall have a noexcept-specification which is equivalent to noexcept(true).

2003年当時の提案文書N1479 III. Design Decisions より引用(下線部は強調)。

Empty arrays
Consideration should be given to the case where N == 0. For traditional arrays this is an error that should be diagnosed by the compiler. It would be reasonable to retain this behaviour

An alternative offerred by the proposal is to partially specialize the template on the case N == 0, such that it has no internal state, empty() == true, and begin() == end() == 0.

This solution preferred by the proposal removes a potential error from library use. This may be particularly valuable when writing generic code parameterized on N. However, it is a change of behaviour compared to the traditional C array.

関連URL

*1:C言語においても、C言語組込の配列要素数には正数しか許容されない。構造体のフレキシブル配列メンバ(flexible array member)は「要素数ゼロの配列」と似ているが、文法的には配列要素数を指定しない不完全配列型として宣言する必要がある。JPCERT DCL38-C も参照のこと。

配列添字演算子の小さな改善案 for C2y

プログラミング言語Cにおける配列添字演算子(array subscript operator)[]の伝統的かつ奇妙な言語仕様に関して、次期C2yをターゲットとした仕様修正が提案されている。小ネタ以外の使い道もなく修正されて良い頃合いかもね?*1

const char msg[] = "Hello";
const char *p = msg + 1;
assert( 1[msg] == 'e' );  // OK: msg[1]と等価
assert( msg[-1] != 0 );   // NG: 未定義動作
assert( p[-1] == 'H' );   // OK: *(p-1)と等価

// N3352 2.2.1 Option1適用後
// 式 E[N] の E は配列型/ポインタ型のいずれか
assert( 1[msg] == 'e' );  // NG: ill-formed
// 配列型Eと整数定数Nの場合は0≦N
assert( msg[-1] != 0 );   // NG: ill-formed
assert( p[-1] == 'H' );   // OK: *(p-1)と等価

C23(N3220) 6.5.3.2/p2より引用(下線部は強調)。C90/C99/C11/C17でも同様。

A postfix expression followed by an expression in square brackets [] is a subscripted designation of an element of an array object. The definition of the subscript operator [] is that E1[E2] is identical to (*((E1)+(E2))). Because of the conversion rules that apply to the binary + operator, if E1 is an array object (equivalently, a pointer to the initial element of an array object) and E2 is an integer, E1[E2] designates the E2-th element of E1 (counting from zero).

C Rationale 6.5.2.1より一部引用。

The C89 Committee found no reason to disallow the symmetry that permits a[i] to be written as i[a].

関連URL

*1:C言語 FAQ 日本語訳, 6章 配列とポインター, 6.11: 「このとんでもない交換可能性は、よくC言語について扱う文章の中で、誇らしく思うかのように記述されているが国際難解Cプログラムコンテスト以外では役に立たない。」