Rokiのチラ裏

学生による学習のログ

少し話題になったP0145R3を読む

少し話題になったとは:ツイッターで出題した未定義問題のお詫びと調査と解説について - paiza開発日誌

折角なのでこの機会にP0145R3の訳をざっくり書く事にした。

Abstract and Introduction

そもそもP0145R3とは何についてのpaperなのか。

This paper proposes an order of evaluation of operands in expressions, directly supporting decades-old established and recommended C++ idioms. - P0145R3 Abstract

P0145R3は、オペランドの評価の順序を定めようという提案書。イントロダクションでは、評価がunsequencedであるがために起きると考えられる具体例などが述べられている。

Order of expression evaluation is a recurring discussion topic in the C++ community. In a nutshell, given an expression such as f(a, b, c), the order in which the sub-expressions f, a, b, c (which are of arbitrary shapes) are evaluated is left unspecified by the standard. If any two of these sub-expressions happen to modify the same object without intervening sequence points, the behavior of the program is undefined. For instance, the expression f(i++, i) where i is an integer variable leads to undefined behavior, as does v[i] = i++. Even when the behavior is not undefined, the result of evaluating an expression can still be anybody’s guess. Consider the following program fragment:- P0145R3 1. INTRODUCTION

元々、C++における式の評価順序は、C++コミュニティ中で繰り返し討論される話題であるとの事。簡単に言えば、

f(a,b,c)

のような式が与えられると、部分式fabc(任意の形状)が評価される順序は、規格によってunspecifiedのままである。これらのサブ式のいずれか2つが、sequence pointなしに同オブジェクトを変更する場合、プログラムの動作はUBである。 例えば、式

f(i++,i) //(i is an integer variable)

は、

v[i]=i++

と同じくUBに繋がる。
式がunspecifiedでなくとも、式を評価した結果は誰でも推測できる。例えば:

#include <map>
int main() {
    std::map<int, int> m;
    m[0] = m.size(); // #1
}

♯1とマークされている文の評価後、マップオブジェクトmはどのように見えるべきだろうか? {{0、0}}または{{0、1}}だろうか。
以上が、式の評価がunsequencedであるがために起こり得る具体例である。

A corroding problem

These questions aren’t for entertainment, or job interview drills, or just for academic interests. The order of expression evaluation, as it is currently specified in the standard, undermines advices, popular programming idioms, or the relative safety of standard library facilities. The traps aren’t just for novices or the careless programmer. They affect all of us indiscriminately, even when we know the rules. Consider the following program fragment: - P0145R3 2. A CORRODING PROBLEM

これらの問題は、娯楽や面接の練習、または学問的興味のためだけでない。標準で現在指定されているように、式の評価の順序は、教育時の正確性、一般的なプログラミングのイディオム、または標準ライブラリ機能の相対的安全性を損なう。このような罠は、初心者や不注意なプログラマーにのみ影響が渡るのではなく、例え規則を知っていても、この問題は全員に無差別に影響する。例えば:

void f()
{
    std::string s = “but I have heard it works even if you don’t believe in it”;
    s.replace(0, 4, “”).replace(s.find(“even”), 4, “only”).replace(s.find(“ don’t”), 6, “”);
    assert(s == “I have heard it works only if you believe in it”);
}
The assertion is supposed to validate the programmer’s intended result. It uses “chaining” of member function calls, a common standard practice. This code has been reviewed by C++ experts world-wide, and published (The C++ Programming Language, 4th edition.) Yet, its vulnerability to unspecified order of evaluation has been discovered only recently by a tool. Even if you would like to blame the “excessive” chaining, remember that expressions of the form std::cout << f() << g() << h() usually result in chaining, after the overloaded operators have been resolved into function calls. It is the source of endless headaches. Newer library facilities such as std::future are also vulnerable to this problem, when considering chaining of the then() member function to specify a sequence of computation. The solution isn’t to avoid chaining. Rather, it is to fix the problem at the source: refinement of the language rules.- P0145R3 2. A CORRODING PROBLEM

assertマクロは、プログラマの意図を検証している。これは、メンバー関数呼び出しの「連鎖」を使用している*1このコードは、C++の専門家によって世界中でレビューされ、公開されている(The C++ Programming Language, 4th edition)。しかし、評価の不特定の順序に対する脆弱性は、ツールによって最近発見された。このような"過度な"連鎖をしなくても、例えば次のような式は関数呼び出しの連鎖である事に留意しなければならない。

std::cout<<f()<<g()<<h()

std::futureのような新しいライブラリ関数は、then()メンバ関数の連鎖から一連の計算結果を得ようとする事は、この問題の影響を受け易い。この問題に対する解決策は連鎖を避けることではない。言語規則の洗練によって解決されるべきである。

Why now?

なぜ今なのか。A corroding problemと大体同じような事が扱われているので一部だけ。

The current rules have been in effect for more than three decades. So, why change them now? Well, a programming language is a set of responses to challenges of its time. Many of the existing rules regarding order of expression evaluation made sense when C was designed and in the constrained environment where C++ was originally designed and implemented. Some of the justifications probably still hold today. However, a living and evolving programming language cannot just hold onto inertia. - P0145R3 WHY NOW?

30年以上に渡って有効である現在の規則を、何故今変更するのか。それは、プログラミング言語は、その時代で解決しなければならない課題への問題解決法の集合だからである。表現評価の順序に関する既存の規則の多くは、Cが設計されたとき、及びC++が設計され実装され始めていた現代とは異なる制約のある環境で意味があった。恐らく今日のこの規則に対する一定の正当化によって、この規則は未だ保持されている。しかし、今までの慣性を保持するだけでは、生き生きと進化するプログラミング言語であるとは言えない。

The language should support contemporary idioms. For example, using << as insertion operator into a stream is now an elementary idiom. So is chaining member function calls. The language rules should guarantee that such idioms aren’t programming hazards. We have library facilities (e.g. std::future) designed to be used idiomatically with chaining. Without the guarantee that the obvious order of evaluation for function call and member selection is obeyed, these facilities become traps, source of obscure, hard to track bugs, facile opportunities for vulnerabilities. - P0145R3 WHY NOW?

言語は、現代的なイディオムをサポートすべきである。例えば、挿入演算子としてストリームに<<を用いる事は、今や基本的なイディオムである。それに従って、メンバ関数を連鎖させる事となる。言語規格は、そのようなイディオムがプログラムに危険性を孕まない事を保証するべきである。我々は連鎖で慣用的に使用されるように設計されたライブラリ機能(例えばstd::future)を持っている。関数呼び出しとメンバ選択の明示的な評価順序に従うという保証がなければ、これらの機能は罠、曖昧さの原因となり、バグの追跡が難しくなり、容易に脆弱性を生み出してしまう可能性ともなる。

A Solution

解決策。

We propose to revise C++ evaluation rules to support decades-old idiomatic constructs and programming practices. A simple solution would be to require that every expression has a well-defined evaluation order. That suggestion has traditionally met resistance for various reasons. Rather, this proposalsuggests a more targeted fix:

  • Postfix expressions are evaluated from left to right. This includes functions calls and member selection expressions.
  • Assignment expressions are evaluated from right to left. This includes compound assignments.
  • Operands to shift operators are evaluated from left to right.

In summary, the following expressions are evaluated in the order a, then b, then c, then d:
  1. a.b
  2. a->b
  3. a->*b
  4. a(b1, b2, b3)
  5. b @= a
  6. a[b]
  7. a << b
  8. a >> b
Furthermore, we suggest the following additional rule: the order of evaluation of an expression involving an overloaded operator is determined by the order associated with the corresponding built-in operator, not the rules for function calls. This rule is to support generic programming and extensive use of overloaded operators, which are distinctive features of modern C++.
- P0145R3 A SOLUTION

C++の評価ルールを改訂して、何十年も前の慣用的な構造やプログラミング手法をサポートすることを提案する。簡単な解法は、すべての式が明確な評価順序を持つことを要求する事である。その提案は今まで様々な理由で却下されてきたが、この提案は、より焦点を絞った修正を提案する:

  • 後置式は左から右に評価される。これには、関数呼び出しとメンバ選択式が含まれる。
  • 代入式は右から左へ評価される。これには、複合的(連続的)な割り当てが含まれる。
  • シフト演算子オペランドは左から右に評価される。


要約すると、a、b、c、dの順に次の式(上記)が評価される。
さらに、次の追加ルールを提案する:オーバーロードされた演算子を含む式の評価順序は、関数呼び出しのルールではなく、対応するビルトイン演算子に関連付けられた順序によって決定される。
このルールは、包括的なプログラミングと、最新のC++の特長であるオーバーロードされた演算子の広範的な使用をサポートするためである。

A second, subsidiary proposal replaces the evaluation order of function calls as follows: the function is evaluated before all its arguments, but any pair of arguments (from the argument list) is indeterminately sequenced; meaning that one is evaluated before the other but the order is not specified; it is guaranteed that the function is evaluated before the arguments. This reflects a suggestion made by some members of the Core Working Group. - P0145R3 A SOLUTION

2つ目の補助提案は、関数呼び出しの評価順序を次のように置き換える:関数は全ての引数より前に評価されるが、引数リスト中の任意の引数のペアは不確定に順序付けられる。これは、一方が他方の前に評価されるが、順序は指定されないことを意味する。これにより、関数が引数の前に評価されることが保証される。これは、Core Working Groupのメンバーによって提案されたことを反映している。

POSTFIX INCREMENT AND DECREMENT

後置インクリメント、デクリメント。

At the Fall 2014 meeting in Urbana, IL, Clark Nelson observed that the proposal does not suggest when side effects of postfix increment and postfix decrement are “committed”. Indeed, the current proposal does not suggest any particular modification to the sequencing of unary expressions. The primary reason is that we have not found a choice that will support an existing widely used programming idiom or nurture new programming techniques. Consequently, at this point, we do not propose any change to unary expressions. The side effects of unary expressions shall be committed before the next expression (if any) is evaluated if it is part of a binary expression or a function call. The sequencing order of unary expressions is not changed by this proposal. - P0145R3 POSTFIX INCREMENT AND DECREMENT

Clark Nelson氏は、2014年秋のUrbanaでの会議で、後置インクリメント、デクリメントの副作用がコミットされるかについて提案していないことを確認した。実際のところ、現在の提案は、単項式の順序付けに対する特別な変更を示唆していない。主な理由は、既存の広く使用されているプログラミングイディオムをサポートすべきか、新しいプログラミング技術を育成する事を優先すべきか、判断しかねるからである。したがって、この時点では、単項式への変更は提案しない。単項式の副作用は、次の式がある場合には、それがバイナリ式、または関数呼び出しの一部である場合に、評価される前にコミットされる。よって単項式の順序付けは、この提案によって変更されない。

誤った解釈があればご教示くださると有難い。

*1:訳注:連鎖:=連続的なメンバ関数呼び出し。e.g. f().g().h()のような。