yohhoyの日記

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

swap(T, U)とis_swappable_with<T, U>とvector<bool>

C++17標準ライブラリには「型が異なる変数間での値交換(swap)」可能か否かを判定するメタ関数std::is_(nothrow_)swappable_with<T, U>が存在する。一般的には値交換操作は同一型変数間(swap(T&, T&))で行われるが、プロキシ型(proxy)のような特殊ケースにおいて異型変数間での値交換(swap(T, U))が必要となるため。*1

// <type_traits>ヘッダ
namespace std {
  template <class T, class U>
  struct is_swappable_with;

  template <class T, class U>
  struct is_nothrow_swappable_with;
}

C++17 20.5.3.2/p5 Exampleを一部転用したコード例:

#include <type_traits>
#include <utility>

namespace N {
  struct A { int m; };
  struct Proxy { A* a; };
  Proxy proxy(A& a) { return Proxy{ &a }; }
  void swap(A& x, Proxy p) {
    std::swap(x.m, p.a->m);
  }
  void swap(Proxy p, A& x) { swap(x, p); }
}

N::A a1 = { 1 }, a2 = { 2 };
auto p2 = N::proxy(a2);

// N::A& と N::Proxy 間で値交換可能
static_assert(std::is_swappable_with_v<N::A&, N::Proxy>);

swap(a1, p2);  // OK
assert(a1.m == 2 && a2.m == 1);

std::vector<bool>コンテナ

(一部で悪名高い)std::vector<bool>コンテナクラスはこのようなプロキシ型を利用する。同コンテナはbool値のビット単位管理によりメモリを効率的に利用できるが*2、その代償としてbool型要素への参照bool&を直接返せないため、要素への添字アクセスv[0]などはプロキシ型vector<bool>::referenceを返す実装となっている。

#include <vector>
#include <utility>

std::vector<bool> v{ true };
bool b = false;

static_assert(
  std::is_swappable_with_v<std::vector<bool>::reference, bool&>
);  // OK?

swap(v[0], b);  // OK?

上記コードはvector<bool>プロキシ型とbool型変数が値交換可能であることを期待するが、C++17現在の標準ライブラリ仕様では該当コードの動作を保証しないGCC/libstdc++*3、Clang/libc++*4ではコンパイル&実行可能だが、MSVC 19.16ではコンパイルエラーとなる。*5

この問題は P0022R2, 3.1 Proxy Iterator problems にて言及されている。

For all its problems, vector<bool> works surprisingly well in practice, despite the fact that fairly trivial code such as below is not portable.

std::vector<bool> v{true, false, true};
auto i = v.begin();
bool b = false;
using std::swap;
swap(*i, b);  // Not guaranteed to work.

Because of the fact that this code is underspecified, it is impossible to say with certainty which algorithms work with vector<bool>. That fact that many do is due largely to the efforts of implementors and to the fact that bool is a trivial, copyable type that hides many of the nastier problems with proxy references. For more interesting proxy reference types, the problems are impossible to hide.

関連URL

*1:本記事は nakameguro_feature.cpp vol.17 勉強会で取り上げられた疑問がきっかけ。

*2:一般的なC++処理系では、1バイト中に8個の bool 値を詰め込むことで消費メモリサイズを節約できる。

*3:https://github.com/gcc-mirror/gcc/commit/5345c53733c161a7781dd55559a4e1458751da1d

*4:https://github.com/llvm-mirror/libcxx/blob/bc8d3f97eb5c958007f2713238472e0c1c8fe02c/include/__bit_reference#L75-L93

*5:https://gcc.godbolt.org/z/Ts_vLF

Goodbye "bit" in C++, (Partially)

C++2a(C++20)言語仕様の定義においては、用語 "bit" の利用はできるだけ回避される(完全に無くなる訳ではない)。これはC++2a言語仕様変更「符号付き整数型==2の補数表現を保証」の影響。

提案文書 P1236R1 Alternative Wording for P0907R4 Signed Integers are Two's Complement 冒頭部より引用。

This paper presents alternative wording for P0907R3 Signed Integers are Two's Complement by Jean François Bastien, avoiding talking about unobservable bits as much as possible.

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1236r1.html

整数型サイズの表現では、独自の用語 "range exponent" が利用される。N4800(C++2a WD) 6.7.1/p4より一部引用。

Table 10 -- Minimum range exponent

Type Minimum range exponent N
signed char 8
short 16
int 16
long 32
long long 64

The range exponent of each signed integer type shall not be less than the values specified in Table 10. The value representation of a signed or unsigned integer type comprises N bits, where N is the respective range exponent. Each set of values for any padding bits (6.7) in the object representation are alternative representations of the value specified by the value representation. (snip)

関連URL

ラムダキャプチャ中での前置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万個のトークンを抽出。