yohhoyの日記

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

ラムダキャプチャ中での前置ellipsisと後置ellipsis

プログラミング言語C++において可変長引数テンプレートパラメータパックをラムダ式でキャプチャする際、そのキャプチャ方式によってellipsis(...)の前置/後置が異なることに注意。

template <typename... Ts>
void func(Ts... xs)
{
  // C++11以降: 簡易キャプチャ(simple-capture)
  [xs...] {
    // xsの各要素をコピーキャプチャ
  };

  [&xs...] {
    // xsの各要素を参照キャプチャ
  };

  // C++2a: 初期化キャプチャ(init-capture)
  [...xs = std::move(xs)] {
    // xsの各要素をムーブキャプチャ
  };
}

N4800(C++2a WD) 7.5.5.2 構文規則, p17より一部引用。
→ 2021-05-11追記:本記事の内容には直接影響しないが、C++20言語仕様では P2095R0 採択により下記引用と少し異なる文法定義となっている。初期化キャプチャ中における参照キャプチャのパック展開構文を[&...xs = init]へ修正している。

lambda-capture :
  capture-list
  (snip)
capture-list :
  capture
  capture-list , capture
capture :
  simple-capture ...opt
  ...opt init-capture

A simple-capture followed by an ellipsis is a pack expansion (12.6.3). An init-capture preceded by an ellipsis is a pack expansion that introduces an init-capture pack (12.6.3) whose declarative region is the lambda-expression's compound-statement. [Example:

template<class... Args>
void f(Args... args) {
  auto lm = [&, args...] { return g(args...); };
  lm();

  auto lm2 = [...xs=std::move(args)] { return g(xs...); };
  lm2();
}

-- end example]

メモ:P0780R1時点では初期化キャプチャ(init-capture)も後置ellipsis方式だったが、最終的に前置ellipsisに変更されてC++2a言語仕様に採択された。“導入される名前直前にellipsisを記述” という既存構文との一貫性を重視したのこと。*1

関連URL

*1:P0780R2: "Following Core and Evolution guidance, the ellipses for an init-capture pack have been moved from following the init-capture to preceding it. This is consistent with the existing practice of ... preceding the name that it introduces."

SecureStringクラスは非推奨

.NET Frameworkで提供される System.Security.SecureStringクラス は、新規コードでは利用しないこと。

Important

We don't recommend that you use the SecureString class for new development. For more information, see SecureString shouldn't be used on GitHub.

https://docs.microsoft.com/en-us/dotnet/api/system.security.securestring

Recommendation

Don't use SecureString for new code. When porting code to .NET Core, consider that the contents of the array are not encrypted in memory.
The general approach of dealing with credentials is to avoid them and instead rely on other means to authenticate, such as certificates or Windows authentication.

DE0001 SecureString shouldn't be used

関連URL

オブジェクトの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 が登場することになる。