yohhoyの日記

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

std::monostateのハッシュ値

C++標準ライブラリの直和データ型std::variant<...>と組み合わせて空の状態を表すstd::monostateオブジェクトでは、std::hashによるハッシュ計算がサポートされる。*1

C++処理系で算出されるハッシュ値の一覧(括弧内は併記コメント/定数名):

  • GCC: -7777(__magic_monostate_hash)*2
  • Clang: 66740831(return a fundamentally attractive random value.)*3
  • MSVC: 1729(Arbitrary value*4

C++20 20.7.8, 20.7.12より引用。

struct monostate{};

The class monostate can serve as a first alternative type for a variant to make the variant type default constructible.

template<class... Types> struct hash<variant<Types...>>;

The specialization hash<variant<Types...>> is enabled (20.14.18) if and only if every specialization in hash<remove_const_t<Types>>... is enabled. The member functions are not guaranteed to be noexcept.

template<> struct hash<monostate>;

The specialization is enabled (20.14.18).

関連URL

TemplateParam = void

C++ライブラリのクラステンプレート設計で見られる、型テンプレートパラメータへのvoidデフォルト指定に関するメモ。

template<class T = void>
class SomeClass;

C++2b(C++23)標準ライブラリ時点では、下記パターンでの用法が存在する。

  • デフォルトvoid型:std::enable_ifC++11から)
  • ジェネリックメンバ関数std::plusなど(C++14から)
  • 型消去(type erasure):std::coroutine_handleC++20から)
  • 複雑なデフォルト型:std::generatorなど(C++2bから)

デフォルトvoid型

テンプレートパラメータ省略時のデフォルトをvoid型をとする、基本のパターン。

// <type_traits>
template<bool, class T = void> struct enable_if;

N4928 21.3.8.7より一部引用。

If B is true, the member typedef type denotes T; otherwise, there shall be no member type.

ジェネリックメンバ関数

クラステンプレートに対象型を明示する代わりに、関数呼び出しの実引数から型推論可能なメンバ関数テンプレートを提供する。

// <functional>
template<class T = void> struct plus;
// 同様に minus, multiplies, divides, modulus
// negate, equal_to, not_equal_to, greater, less, greater_equal, less_equal
// logical_and, logical_or, logical_not, bit_and, bit_or, bit_xor, bit_not
// <memory>のowner_less

C++14にて、2項演算や単項演算を行うファンクタクラス群で全面的に採用された。N4928 22.10.7.2より一部引用。

template<class T = void> struct plus {
  constexpr T operator()(const T& x, const T& y) const;
};
template<> struct plus<void> {
  template<class T, class U> constexpr auto operator()(T&& t, U&& u) const
    -> /*(snip)*/;
};

型消去(Type erasure)

具象型を指定したオブジェクトと、同オブジェクトから変換可能な型消去バージョンを提供する。

template<class Promise = void>
struct coroutine_handle;

例えばPromise型を明示したstd::coroutine_handle<MyPromise>からは、型消去されたstd::coroutine_handle<>へと暗黙変換可能。N4928 17.12.4.1より一部引用。

template<>
struct coroutine_handle<void> {
  // (snip)
};

template<class Promise>
struct coroutine_handle {
  // (snip)
  constexpr operator coroutine_handle<>() const noexcept;
  // (snip)
};

複雑なデフォルトパラメータ

他テンプレートパラメータに基づくデフォルトの型を、(テンプレート宣言では記述できない)複雑なルールで導出する。

// <generator>
template<class Ref, class V = void, class Allocator = void>
class generator;
// <memory>
template<class Pointer = void, class Smart, class... Args>
auto out_ptr(Smart& s, Args&&... args);
template<class Pointer = void, class Smart, class... Args>
auto inout_ptr(Smart& s, Args&&... args);

例えばジェネレータコルーチン戻り値型std::generator(→id:yohhoy:20220801)では、デフォルトの参照型/値型/アロケータ型指定のために本パターンを利用する。N4928 26.8.3より一部引用。

template<class Ref, class V = void, class Allocator = void>
class generator : public ranges::view_interface<generator<Ref, V, Allocator>> {
private:
  using value = conditional_t<is_void_v<V>, remove_cvref_t<Ref>, V>;
  using reference = conditional_t<is_void_v<V>, Ref&&, Ref>;
  // (snip)
};

変数型のコンパイル時判定

プログラミング言語C++において、コンパイル時にある変数の宣言型を判定する方法あれこれ。

// 型名T と 変数名val

// C++11以降
#include <type_traits>
static_assert(std::is_same<T, decltype(val)>::value, "");

// C++17以降
#include <type_traits>
static_assert(std::is_same_v<T, decltype(val)>);

// C++20以降
#include <concepts>
static_assert(std::same_as<T, decltype(val)>);

// C++2b(C++23)以降
#include <typeinfo>
static_assert(typeid(T) == typeid(val));

ノート:typeid演算子ではトップレベルのCV修飾と参照修飾が無視されるため(→id:yohhoy:20121122)、型情報の厳密一致を判定するstd::is_samestd::same_asとは挙動が微妙に異なることに注意。

関連URL

pthread_exit on main thread

POSIX Pthreadsライブラリのpthread_exit関数とメインスレッドとの関係について。本記事の内容はStackOverflowで見つけた質問と回答に基づく。

まとめ:

  • exit関数で終了:他のスレッドが実行中であってもプロセス終了する。
    • 注:main関数からのreturnはexit関数呼び出しと等価(→id:yohhoy:20120607)。
  • pthread_exit関数で終了:実行中の他スレッド処理は継続する。全スレッドが終了すると、プロセスも終了する。
    • C11標準ライブラリthrd_exit関数には関連記述みあたらず...
    • C++11標準ライブラリでは相当機能なし。*1

例えばLinuxのPthreads実装では、メインスレッドからのpthread_exit関数呼び出しについて明言されている。

NOTES
Performing a return from the start function of any thread other than the main thread results in an implicit call to pthread_exit(), using the function's return value as the thread's exit status.

To allow other threads to continue execution, the main thread should terminate by calling pthread_exit() rather than exit(3).

(snip)

pthread_exit(3) - Linux manual page

関連URL

*1:C++では関数からのreturn以外にスレッドを終了させる手段が提供されない。

非型テンプレートパラメータでのオーバーロード解決は行われない

関数テンプレートの非型テンプレートパラメータ(non-type template parameter)において、通常のC++オーバーロード解決規則は適用されない。沼に近よるべからず ('ω'乂)

template <bool N> void f() {}  // #1
template <int N>  void f() {}  // #2

f<0>();  // NG: オーバーロード解決は曖昧
f<1>();  // NG: オーバーロード解決は曖昧
f<false>();  // NG: オーバーロード解決は曖昧
f<true>();   // NG: オーバーロード解決は曖昧

f<2>();  // OK: #2を呼び出す
void g(bool);  // #1
void g(int);   // #2

g(0);  // OK: #2を呼び出す
g(1);  // OK: #2を呼び出す
g(false);  // OK: #1を呼び出す
g(true);   // OK: #1を呼び出す

g(2);  // OK: #2を呼び出す

You can declare them; you can't really use the bool one, because there's no mini-overload-resolution for template arguments; everything that's a valid converted constant expression of the template parameter's type is equally good. Regardless, if you actually see a negative impact from dummy parameters passed to control overload resolution, you should be filing bugs with your compiler vendor, not contorting your code (further).

https://stackoverflow.com/questions/34972679/#comment57675229_34972679

関連URL

std::views::splitとstd::views::lazy_split

C++20標準ライブラリ提供レンジアダプタstd::views::splitstd::views::lazy_splitの比較。*1

まとめ:

  • 入力レンジを区切りパターン(単一要素またはレンジ)を用いて分割し、「部分レンジ(subrange)のレンジ」へと変換するレンジアダプタ(range adaptor)。*2
  • 大半のユースケースsplitを用いればよい。
    • 使い勝手を重視:部分レンジ(std::ranges::subrange)への分割処理を遅延評価する。*3
    • 入力レンジの特性が出力レンジへと引き継がれる。(例:文字列の分割結果を文字列として扱える)
  • 特殊ケースに対してのみlazy_splitを検討する。
    • 一般性を重視:最大限に怠惰(lazy)な分割アルゴリズムを用い、遅延生成される部分レンジを遅延生成する。
    • 入力がinput_rangeの場合や、出力レンジを Const-iterable としたい場合など。

入力レンジとパターンの制約

分割対象Vと区切りパターンPatternに対するテンプレートパラメータ制約は次の通り。

  • split: Vはforward_rangeを満たすview。
  • lazy_split: Vはforward_rangeを満たすview、またはVはinput_rangeを満たすviewかつPatternが確定サイズ1要素以下のレンジ。*4
  • 共通: Patternはforward_rangeを満たすview。

入出力レンジコンセプト

レンジアダプタの適用対象Vが満たすコンセプトに対して、適用結果の外部(Outer)レンジ/内部(Inner)レンジが満たすコンセプトは下表の通り。*5

入力\出力 split
Outer
split
Inner
lazy_split
Outer
lazy_split
Inner
input *6 (N/A) (N/A) input input
forward forward forward forward forward
bidirectional forward bidirectional forward forward
random_access forward random_access forward forward
contiguous forward contiguous forward forward

Const-iterable性

レンジアダプタ適用後レンジの const-iterable 性(→id:yohhoy:20210701)は次の通り。

  • split: 適用結果は const-iterable ではない。
  • lazy_split: 対象Vがforward_rangeを満たすならば、適用結果は const-iterable となる。input_rangeの場合は 非const-iterable。

関連URL

*1:ISO C++20発行後に P2210R2 が Defect Report として遡及適用されている。

*2:C++標準レンジアダプタはいずれも遅延評価を行う。実際にレンジ要素へのアクセスが必要になるまで、レンジアダプタによる各種アルゴリズムは実行されない。

*3:https://wandbox.org/permlink/1ebAmAsmrqLrLFQ8

*4:split, lazy_split ともに、確定サイズ0要素の区切りパターン(例:std:::ranges::empty<T>)によるレンジ分割にも対応する。結果として、内部(Inner)レンジ==対象レンジ要素を1個づつ取り出した単一要素レンジが得られる。

*5:表中の input は std::ranges::input_range コンセプトの略記。他 forward, bidirectional, random_access, contiguous も同様。

*6:区切りパターンに任意の forward レンジを指定できず、確定サイズが1要素以下のレンジ指定が必要となる。

... in Python3

Python3のEllipsisリテラル...の一風変わった使い方。擬似コード風のプレースホルダとして利用可能。

def func():
   ...

# 以下と等価
# def func():
#   pass

2008年1月のPython3検討メーリングリスト*1より、Guido van Rossum*2による投稿を引用。

Some folks thought it would be cute to be able to write incomplete
code like this:

class C:
  def meth(self): ...
  ...

and have it be syntactically correct.

[Python-3000] Ellipsis Literal

関連URL

*1:Python 3.0の仕様検討段階では「Python 3000」や「Py3K」と呼称されていた。

*2:wikipedia:ja:グイド・ヴァンロッサム