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 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);



#include <vector>
#include <utility>

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

  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.


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

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