yohhoyの日記

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

オブジェクトのDrop順序

プログラミング言語Rustにおける、オブジェクトデストラクト(Drop)順序についてメモ。[本記事はRust 1.32/Stable準拠]

  • 変数は、その変数宣言順序の逆順。
    • ただし同一パターン内の変数同士では、順序未規定(unspecifined)。
  • 構造体(struct)、タプル、列挙型(enum)のフィールドは、そのフィールド宣言順序の通り。
  • 配列[T; n]Box<[T]>の要素では、インデクス昇順。
    • 可変長配列Vecの要素では、順序未規定(unspecifined)。
  • クロージャにキャプチャされた値は、順序未規定(unspecifined)。

これらのDrop順序を明示的に変更したい場合、std::mem::ManuallyDrop(+unsafeブロック)を用いてデストラクト順を制御することもできる。

The destructor of a type consists of

  • Calling its std::ops::Drop::drop method, if it has one.
  • Recursively running the destructor of all of its fields.
    • The fields of a struct, tuple or enum variant are dropped in declaration order. *
    • The elements of an array or owned slice are dropped from the first element to the last. *
    • The captured values of a closure are dropped in an unspecified order.
    • Trait objects run the destructor of the underlying type.
    • Other types don't result in any further drops.

* This order was stabilized in RFC 1857.


Variables are dropped in reverse order of declaration. Variables declared in the same pattern drop in an unspecified ordered.

https://doc.rust-lang.org/reference/destructors.html

Vec does not currently guarantee the order in which elements are dropped. The order has changed in the past and may change again.

https://doc.rust-lang.org/std/vec/struct.Vec.html

関連URL

Andrew’s C/C++ Token Count Dataset 2016

プログラミング言語C/C++トークン(記号・キーワード・識別子名など)出現頻度を調査したデータセット。約1.1万のOSSパッケージ/256万行のソースコードから5032万個のトークンを抽出。

C++ Conceptsの短縮構文(P1141R2)

次期C++2a(C++20)標準規格に向けて採択された、コンセプト(concept)関連の短縮構文についてメモ。C++コンセプト概略ついては id:yohhoy:20170904 を参照のこと。

本記事の内容は P1141R2 Yet another approach for constrained declarations に基づく。

要約:

  • autoキーワードによる関数テンプレート定義が可能。“template-less Function Template”
  • 現行autoキーワードを用いた型推論にて、コンセプト名による制約付けが可能。

関数テンプレートパラメータTへのコンセプトC指定では、下記構文のいずれかを用いて制約(constraint)を指定できる。短縮関数テンプレート(abbreviated function template)構文が新たに追加され、templateキーワードを用いない関数テンプレート定義が可能となる。(→id:yohhoy:20150303

// C++2a
// 型パラメータTに対するコンセプトC
template <typename T> concept C = /*...*/;

// A) 制約付きパラメータ(constrained-parameter)による制約
template <C T>
void func(T arg);

// B) requires節(requires-clause)による制約
template <typename T> requires C<T>
void func(T arg);

// C) 末尾requires節(trailing requires-clause)による制約
template <typename T>
void func(T arg) requires C<T>;

// D) ★短縮関数テンプレート(abbreviated function template)構文
void func(C auto arg);

// D') ★コンセプト制約なし短縮関数テンプレート
void func1(auto arg);

従来からパラメータ型宣言でautoキーワードを利用していたジェネリックラムダ(generic lambda)でも同様の構文となる。

// C++14以降: ジェネリックラムダ
auto lm0 = [](auto arg) { /*...*/ };
// C++2a: ジェネリックラムダのテンプレート構文
auto lm0 = []<typename T>(T arg) { /*...*/ };

// C++2a: テンプレートパラメータ型制約付きジェネリックラムダ(4パターン)
auto lm1 = []<C T>(T arg) { /*...*/ };
auto lm1 = []<typename T> requires C<T> (T arg) { /*...*/ };
auto lm1 = []<typename T> (T arg) requires C<T> { /*...*/ };
auto lm1 = [](C auto arg) { /*...*/ };  // ★P1141R2で導入

autoキーワードを用いた関数の戻り値型推論や変数型推論においても、推論される型に対してコンセプトCによる制約付けが行える。いずれもautoキーワードは必須であり、コンセプト名のみ(C f2();C v2;)ではill-formedとなる。

// 戻り値型に制約なし
auto f0() {
  return /*...*/;
}
decltype(auto) h0() {
  return /*...*/;
}
// 変数型に制約なし
auto v0 = f0();

// C++2a: 戻り値型はコンセプトCを満たすべき
C auto f1() {
  return /*...*/;
}
C decltype(auto) h1() {
  return /*...*/;
}
// C++2a: 変数型はコンセプトCを満たすべき
C auto v1 = f1();

関連URL

関数テンプレート特殊化とADLの小改善

C++2a(C++20)における特殊化された関数テンプレート呼び出しとADL(Argument Dependent Lookup)に関する小さな改善。

C++17現在の言語仕様では、タプル要素アクセス関数はstd::get<N>のように完全修飾名で呼び出す、もしくはusing std::get;により名前getを現在の名前空間へ取り込んでおく必要がある。C++2a言語仕様ではこの制限が緩和され、ADLによって期待通りstd::get関数テンプレートを見つけるようになる。

#include <tuple>

std::tuple<char, int, double> tp{'X', 42, 3.14};

// C++17: NG 名前getが見つからないためコンパイルエラー
// C++2a: OK 期待通りstd::get関数テンプレートを呼び出す
assert( get<1>(tp) == 42 );

// OK: タプルのインデクス位置1=int型要素にアクセス
assert( std::get<1>(tp) == 42 );
// または
using std::get;
assert( get<1>(tp) == 42 );

C++17仕様

tupleクラスは名前空間stdに属しており、一見するとget<1>(tp)はADLによりstd::get<1>となるように思えるが、関数テンプレートパラメータ明示されている場合はプログラマの待通りに動作しない。

ここではトーク<の存在により、“特殊化されたget<1>関数テンプレートの呼び出し” ではなく “式get < 1 > (tp)の評価*1” と解釈され、ADLは適用されずにgetは未知の名前とみなされる。C++17 17.8.1/p8より引用(下線部は強調)。

[Note: For simple function names, argument dependent lookup (6.4.2) applies even when the function name is not visible within the scope of the call. This is because the call still has the syntactic form of a function call (6.4.1). But when a function template with explicit template arguments is used, the call does not have the correct syntactic form unless there is a function template with that name visible at the point of the call. If no such name is visible, the call is not syntactically well-formed and argument-dependent lookup does not apply. If some such name is visible, argument dependent lookup applies and additional function templates may be found in other namespaces. [Example:

namespace A {
  struct B { };
  template<int X> void f(B);
}
namespace C {
  template<class T> void f(T t);
}
void g(A::B b) {
  f<3>(b);     // ill-formed: not a function call
  A::f<3>(b);  // well-formed
  C::f<3>(b);  // ill-formed; argument dependent lookup applies only to unqualified names
  using C::f;
  f<3>(b);     // well-formed because C::f is visible; then A::f is found by argument dependent
}

-- end example] -- end note]

C++2a仕様

C++2aにむけた提案文書P0846R0が採択され、本ケースのように「後続<がある場合にはテンプレート名とみなす」よう言語仕様が調整された。N4762 Working Draft 6.4.1/p3, 12.2/p2より一部引用(下線部は強調)。

The lookup for an unqualified name used as the postfix-expression of a function call is described in 6.4.2. [Note: For purposes of determining (during parsing) whether an expression is a postfix-expression for a function call, the usual name lookup rules apply. In some cases a name followed by < is treated as a template-name even though name lookup did not find a template-name (see 12.2). For example,

int h;
void g();
namespace N {
  struct A {};
  template <class T> int f(T);
  template <class T> int g(T);
  template <class T> int h(T);
}

int x = f<N::A>(N::A());  // OK: lookup of f finds nothing, f treated as template name
int y = g<N::A>(N::A());  // OK: lookup of g finds a function, g treated as template name
int z = h<N::A>(N::A());  // error: h< does not begin a template-id

(snip) -- end note]

For a template-name to be explicitly qualified by the template arguments, the name must be considered to refer to a template. [Note: Whether a name actually refers to a template cannot be known in some cases until after argument dependent lookup is done (6.4.2). --end note] A name is considered to refer to a template if name lookup finds a template-name or an overload set that contains a function template. A name is also considered to refer to a template if it is an unqualified-id followed by a < and name lookup finds either one or more functions or finds nothing.

関連URL

*1:比較演算子 < として解釈され、左項に未知の名前 get が登場することになる。

OpenMP 5.0仕様リリース

2018年11月 OpenMP 5.0仕様リリース記事 OpenMP 5.0 Is A Major Leap Forward より抄訳。

OpenMP仕様バージョン5.0はOpenMP ARB、主要なコンピュータハードウェア/ソフトウェアベンダのグループ、そしてOpenMPコミュニティのユーザによって共同開発されました。いくつかの小さな改善に加えて、改訂仕様は下記の主要な機能追加を含みます:

  • アクセラレータデバイスのフルサポート。ホストシステムとコプロセッサバイス間の統合共有メモリ(unified shared memory)を必要とする機構、デバイス固有の機能実装を利用する能力、暗黙的データマッピングのより良い制御、実行時にデバイスオフロードを優先する能力を含むことで、OpenMPアクセラレータデバイスを完全にサポートします。さらに、オフロードの反転、暗黙の関数生成、オブジェクト指向データ構造を簡単にコピーする能力もサポートします。
  • デバッグ改善とパフォーマンス解析。新しい2種類のツールインタフェースにより、直感的なデバッグと深いパフォーマンス解析をサポートするサードパーティ製ツール開発が可能となります。
  • 最新のC, C++, Fortranバージョンサポート。いまやOpenMPFortran 2008、C11、C++17の重要な機能をサポートします。
  • 完全に記述的なloopコンストラクトのサポート。loopコンストラクトは、特定の処理系を強制することなく、コンパイラによるループ最適化を促します。このコンストラクトはコンパイラに対して、他のOpenMPディレクティブよりも特定ターゲット向けに良い実装を選択する自由度を与えます。*1
  • マルチレベル・メモリシステム。帯域幅メモリのように、データを異なる種類のメモリ上に配置するメモリ確保機構を利用できます。新しいOpenMP機能によって、NUMA的なモダンHPCシステムの扱いが容易になります。*2
  • ポータビリティの改善。declare variantディレクティブや新しいメタ・ディレクティブにより、プログラマコンパイル時にOpenMPプラグマとユーザコードを合わせることで、パフォーマンス・ポータビリティの向上が可能になります。*3

関連URL

*1:loopコンストラクト:OpenMP5.0で追加された汎用ループ並列化指示構文。

*2:ヘテロジニアス・メニーコアシステム向けのメモリ確保・解放関数が追加される。OpenMP標準規格では5種類のメモリ空間(デフォルト/大容量/定数プール/広帯域幅/低遅延)が事前定義される。

*3:特定のOpenMPコンテキスト(CPU/GPU/FPGAの別/アーキテクチャ/ベンダ情報など)向けに、実装を切り替える機能。

新機能は属性 or 構文 or 関数?

プログラミング言語C++標準化プロセスにおける、新機能の対応方針についての考え方。2018年Jacksonville会合におけるEWG(Evolution Working Group)ガイダンス。

  • 構文上はオブジェクト(object)が対象のように見えるとしても、値(value)に関する属性を導入すべきでない。
  • コンパイラマジック関数で実現可能な機能について、コア言語機能(≒新しい構文)追加を行うべきでない。

C++2a(C++20)標準ライブラリに導入される、アドレス値アラインメント情報をコンパイラに与えるstd::assume_aligned関数の場合、当初提案*1では属性(attribute)とされていたが同ガイダンスに従い関数へと変更された。提案文書P1007R2より一部引用(下線部は強調)。

A previous paper [P0886R0] proposed to add this functionality to C++ through a new standard attribute, [[assume_aligned(N)]]. The guidance given by EWG in Jacksonville (2018) was that having this functionality in C++ is desirable, but not as an attribute. We should not introduce an attribute that appertains to values, even though syntactically it appears to appertain to objects. We also should not add a core language feature if this can be done in the library via a "magic" function.

P1007R2 std::assume_aligned, 4 Previous work and committee guidance

クラステンプレートの非テンプレートメンバ関数でSFINAE

クラステンプレートのテンプレートパラメータに基づき、非テンプレートなメンバ関数オーバーロードでSFINAEを実現する方法。

template<typename T, typename U>
struct X {
  // テンプレートパラメータT, Uによるメンバ関数オーバーロード
  void mf(const T&) { ... }  // A)
  void mf(const U&) { ... }  // B)
};

// NG: メンバ関数A), B)が同一シグネチャとなる
X<int, int> x;

SFINAEは関数テンプレートパラメータ推論時にのみ機能する。下記コードB)は非テンプレートメンバ関数のためill-formed。

template<typename T, typename U>
struct X {
  void mf(const T&) { ... }  // A)

  // 戻り値型でSFINAEするつもりが...
  std::enable_if_t<!std::is_same_v<T, U>>
  mf(const U&) { ... }  // B)
  // NG: "メンバ関数戻り値型が存在しない"状態となりコンパイルエラー
};

X<int, int> x;

強引にSFINAEを有効化するため、B)をメンバ関数テンプレートとして定義する。パターン1では非型テンプレートパラメータAlwaysTruestd::enable_if条件式に含めることで、パターン2ではクラステンプレートパラメータT, Uメンバ関数テンプレートパラメータT1, U1のデフォルト値とすることで、メンバ関数オーバーロード解決時にSFINAEを引き起こす。

template<typename T, typename U>
struct X {
  void mf(const T&) { ... }  // A)

  // メンバ関数テンプレートとして定義する
#if パターン1
  template<bool AlwaysTrue = true>
  std::enable_if_t<!std::is_same_v<T, U> && AlwaysTrue>
  mf(const U&) { ... }  // B) T==Uのときは定義されない
#elif パターン2
  template<
    typename T1 = T, typename U1 = U,
    typename = std::enable_if_t<!std::is_same_v<T1, U1>>
  >
  void mf(const U&) { ... }  // B) T1==U1のときは定義されない
#endif
};

X<int, int> x;
x.mf(42);  // OK: A)を呼び出す

おまけ:C++2a(C++20) Conceptsで導入されるrequires節を利用すると、SFINAEに頼らない関数オーバーロード選択が可能となる。(→id:yohhoy:20170904

// C++2a
template<typename T, typename U>
struct X {
  void mf(const T&) { ... }  // A)

  void mf(const U&)
    requires !std::is_same_v<T, U>
  { ... }  // B) T==Uのときは定義されない
};

X<int, int> x;
x.mf(42);  // OK: A)を呼び出す

関連URL