[PDF注意] N4220: An update to the preprocessor specification (rev. 2)
プリプロセッサーの規格には、未定義な挙動とされている部分があるが、多くの主要な実装で似通った実装になっているところもあるので、その部分を規格で追認する。また、生文字列リテラルやユーザー定義リテラルなど、C++の新しい文法にプリプロセッサー規格が追いついていないので、対応させる。矛盾した点や曖昧点も改善する。
プリプロセッサーの定義に未定義な挙動が多いのは、実装に自由度をもたせたり、独自拡張を許可したりするためだったのだが、結局問題のある未定義の挙動は違法であると定義したほうがよいし、当時は独自拡張のために未定義にしていたところも、数十年たてば、主要な実装はほぼ似たような実装に落ち着いたりした。
[PDF注意] N4221: Generalized lifetime extension
寿命修飾子(lifetime qualifier)の提案。
一時オブジェクトの寿命は、完全な式の中である。
ただし、リファレンスに束縛された一時オブジェクトは寿命が延長される。
struct X { } ;
int main()
{
// 一時オブジェクトの寿命が延長される。nn
auto && ref = f() ;
}
しかし、関数呼び出しの結果返されるリファレンスに対しては、たとえ参照先が一時オブジェクトであったとしても、寿命延長が行われない。
struct X
{
int data ;
~X()
{
std::cout << data << ": destructed.\n" ;
}
} ;
int main()
{
auto && r1 = X{1} ; // 寿命延長される
auto && r2 = std::move( X{2} ) ; // 寿命延長されない
std::cout << "end of scope.\n" ;
}
なぜならば、関数の実引数として与える式がfull-expressionだから、その時点でmain関数のブロックスコープのリファレンスに束縛されていない以上、寿命延長されないのだ。関数を呼び出した結果のリファレンスは、たとえ一時オブジェクトを束縛していたとしても、寿命延長されない。
これにより、クラスのアクセッサー(getter)が使えず、データメンバーを直接publicで使わなければならないという問題もある。
struct X
{
int data ;
int & get_data() const { return data ; }
} ;
int main()
{
auto && ref = X{}.data ; // 寿命延長される
auto && ref = X{}.get_data() ; // 寿命延長されない
}
ここで、dataはprivateなメンバーにして、publicがget_dataでアクセスさせたいとしても、寿命延長されないという点で使えない。
これが具体的に問題になるのは、例えばrange-based forだ。
struct has_vec
{
// これはprivateメンバーにしたい
std::vector<int> m = { 1, 2, 3 } ;
std::vector<int> & get_vec() const
{ return m ; }
} ;
int main()
{
for ( int val : has_vec{}.m ) ; // OK
for ( int val : has_vec{}.get_vec() ) ; // エラー、寿命延長されない
}
もっと具体的な例を挙げると、Boost.Adaptorsをrange-based forで使いたい場合がある。
std::vector<int> vec;
for (int val : vec | boost::adaptors::reversed
| boost::adaptors::uniqued )
{
// 一時オブジェクトはすでに破棄されている
}
関数を超えて一時オブジェクトの寿命を延長させるには、新しい言語機能が必要である。
関数の仮引数宣言に寿命修飾子(lifetime qualifier)であるexportキーワードを記述して、寿命を延長させることができる。
struct X { } ;
template < typename T >
T const & f( export T const & t )
{
return t ;
}
int main()
{
auto && ref = f( X{} ) ; // 寿命延長される
}
データメンバーを直接使わなければならない問題も、寿命修飾子により解決できる。
class has_vec
{
private :
std::vector<int> m = { 1, 2, 3 } ;
public :
std::vector<int> & get_vec() export const
{ return m ; }
} ;
int main()
{
for ( int val : has_vec{}.get_vec() ) ; // OK
}
これにより、Boost.Adaptorsの例も動く。
[PDF注意] N4222: Minimal Additions to the Array View Library for Performance and Interoperability
連続したストレージを多次元配列に見せかけるライブラリarray_viewに対する機能追加の提案。
テンプレートで行が先か、列が先か、レイアウトを選べるようになる。
既存の似たようなライブラリからの移行を用意にするため、operator ()をサポート
operator []はレイアウトが行列どちらでも動作が変わらないようにする。
内部的なポインターにアクセスできるようにする。
特に1次元配列の場合だが、RandomAccessContainerとrangeのサポート。
N4223: Response To: Let return Be Explicit
N4074で提案された、return {expr}で暗黙の型変換がおこるときでもexplicitコンストラクターを呼び出せるようにする提案に対する反論。
explicitなものはexplicitなのだから制限を緩和すべきではないとしている。
N4224: Supplements to C++ Latches
N4204で提案されている同期ライブラリ、ラッチとバリアーに対する機能追加の提案。
ラッチのオブジェクトを安全に破棄するためには、そのラッチに対してwaitしているすべてのスレッドのブロックを解いてからでなければならない。これは、ラッチをローカル変数として確保している場合、そのスレッドはブロックスコープを抜ける前にwaitしなければならないことを意味する。
これを避けるために、以下の関数でSelf-Destructするラッチを生成できるようにする。
static latch* create_self_deleting( int C );
それから、ラッチの完了通知ができるようになった後、スレッドのブロックが解かれる間の状態のときに、呼び出されるコールバック関数を指定できる、flex_latchを追加する。
N4225: Towards uniform handling of subobjects
基本クラスをクラス定義の中で宣言できる機能の提案。
まだ大雑把な構想段階だが、たとえばこういうコードが書けるようになりたい。
struct Base
{
Base( std::string ) ;
} ;
class Derived
{
members:
string str = "hello" ;
public bases :
Base{ str } ;
} ;
アクセス指定子の文法を拡張することで、基本クラスをクラス定義の中で指定できる。
しかも、基本クラスよりも上に書かれているデータメンバーの初期化の方が、基本クラスの初期化より先に行われるので、基本クラスの初期化に派生クラスのデータメンバーを使うことができる。
論文はとても短く、大雑把な構想しか記述していない。そして最後に、
この提案の目的は何か?
簡単に言えば、この提案は複数の欲しがられている機能を汎用的に提供できる、とても大雑把な構想だ。パーサーだとかそのへんの問題はいろいろあるだろうけど、とりあえず目安として、まず聞きたいことは、
- We hate it?
- We like it?
私はもっと具体的な利用例や需要が示されるまでは、いらないと思う。
[PDF注意] N4226: Apply the [[noreturn]] attribute to main as a hint to eliminate global object destructor calls
[[noreturn]]をmainに指定した場合、staticストレージのオブジェクトの破棄をしなくてもよいという意味にしようという提案。
組み込み環境など、mainから戻ることがなく、staticストレージ上のオブジェクトを破棄するコードすら許容できないような厳しい資源の制約のある環境の場合に役に立つ。
私は、別のattributeにしたほうがいいと思う。
[PDF注意] N4227: Cleaning-up noexcept in the Library (Rev 2)
N3279で、標準ライブラリにnoexceptを付ける際のガイドラインを設けた。
数年の経験を経た今、そのガイドラインを改良する必要がある。
問題なのは、デバッグモードでムーブコンストラクターが例外を投げる可能性だ。そこで、C++17では、std::stringのムーブコンストラクターからnoexcetptを取り除く決定がなされた。
しかし、一般にnoexceptを取り除きたくはない。そこで、ガイドラインでは、「noexceptにすることが大変望ましい」として、要求はしないようにする。
[PDF注意] N4228: Refining Expression Evaluation Order for Idiomatic C++
f( a, b, c ) のような式があった場合、sub-expressionであるf, a, b, cの評価順序は、規格上未規定である。もし、sub-expressonが同じオブジェクトを変更していた場合、プログラムの挙動は未定義になってしまう。f( i++, i ) や v[i] = i++ のような式の挙動は未定義である。
例えば、以下のコードは未規定である。
#include <map>
int main() {
std::map<int, int> m;
m[0] = m.size();
}
論文は言う、「この手の質問は、単なる娯楽ではなく、採用面接問題でもなく、学術的な興味によるものでもない。現在規格が規定する式の評価順序は、有害で、有名なプログラミングのイディオムに反し、標準ライブラリの安全な使用にも悪影響を与えている。この罠は素人や注意深くないプログラマーのみ引っかかるものではない。ルールを知っているものですら陥る。」
例えば、以下のコードだ。
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”);
}
このコードの挙動は未規定である。
ちなみに、このコードは世界的なC++エキスパート達から査読を受けた、The C++ Programingu Language 4th(プログラミング言語C++第四版)に存在するものである。最近、このコードの問題がツールによって発見された。
このコードを、トリッキーなメソッドチェイニングを使うから悪いのだと避難するのは当たらない。std::cout << a << b << cですらチェイニングである。また、std::future<T>にthen()メンバー関数を追加して、メソッドチェイニング的に使わせようと設計している今、問題はチェイニングではないのだ。根本的な問題を直さなければならない。
論文では、以下の評価順序を提案している。
- postfix-expressionは左から右に評価される。これには関数呼び出しとメンバーアクセスも含まれる
- 代入式は右から左に評価される。複合代入も含まれる
- シフト演算子のオペランドは左から右に評価される
以下の式は、a, b, c, dの順に評価される。
- a.b
- a->b
- a(b,c,d)
- b=a
- b@=a
また、オーバーロード演算子の評価順序は、関数呼び出しのルールではなくて、対応する組み込み演算子の評価順序に従う。
これは歓迎すべき提案のように思われる。sub-expressionの評価順序は定めるべきだ。
N4229: Pointer Ordering
標準ライブラリの関数オブジェクトの比較はtotal orderingを要求しているが、関数オブジェクトが関数ポインターの場合ですら適用されてしまう。ポインターに対する組み込みの比較はtotal orderingではないというのに。
関数ポインターの場合は、組み込みの比較と同じ結果になるように一文を加える修正。
ドワンゴ広告
この記事はC++WG JPの会議に出た後にドワンゴに出勤して書かれた。
今夜はボドゲでもしようとおもう。
ドワンゴは本物のC++プログラマーを募集しています。
ドワンゴはFPGAの扱えるハードウェア開発エンジニアを募集しているようだ。
【ニコニコ事業】ハードウェア開発エンジニア (正社員)|職種一覧|中途採用・アルバイト採用・障がい者採用|採用情報|株式会社ドワンゴ
CC BY-ND 4.0: Creative Commons — Attribution-NoDerivatives 4.0 International — CC BY-ND 4.0
N4225ですが、継承にAUTOはキモいです。並べるのであれば一個にしたいし、何を継承しているのかわかりにくくなることに違和感を覚えます。逆に何がそこに書いてあるのかが明示できるのであればよい機能に思いますが、コボルのような記述にはなってほしくないです。
ReplyDeleteC++は曖昧であるべきです。曖昧の中に発明があるからです。もちろん明示するところはしないといけませんが、それ以外を厳格化する必要は感じません。
ほかは自分にはムズカシイです。