yohhoyの日記

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

Preconditions: false is true.

C++2b(C++23)向けに提案されているコード不到達表明std::unreachable関数の前提条件。
2022-02-09追記:2022年2月会合にて(PDF)P0627R6採択され、C++2b標準<utility>ヘッダにstd::unreachable関数が追加される。

2022-07-25追記:次世代C言語標準でもC2x(C23)向け提案(PDF)N2826が採択済み。<stddef.h>標準ヘッダの関数マクロ(function-like macro)unreachable()として導入される。

false is true」は常に成り立たない(恒偽命題)ため、unreachable関数呼び出しは必ず未定義動作(undefined behavior)を引き起こす。つまり…どういうことだってばよ?

C++コンパイラは同関数呼び出しコード箇所には到達しないと仮定して最適化を行ってもよい、という規定となっている。提案文書(PDF) P0627R6 7. Proposed Wording より該当箇所を引用(下線部は強調)。

[[noreturn]] void unreachable();

1 Preconditions: false is true. [ Note: This precondition cannot be satisfied, thus the behavior of calling unreachable is undefined. -- end note]
2 [Example:

int f(int x) {
  switch (x) {
  case 0:
  case 1:
    return x;
  default:
    std::unreachable();
  }
}

int a = f(1);  // OK; a has value 1
int b = f(3);  // undefined behavior

-- end example]

なお初期の提案文書では素直に自然言語で記述されていた。(PDF) P0627R3 7. Proposed Wordingより該当箇所を引用。

2 Effects: The behavior of calling unreachable is undefined.

関連URL

2進数フォーマット出力 in 標準C

プログラミング言語Cの次期C2x(C23)標準ライブラリprintf関数ファミリでは、変換指定子bによる2進数フォーマット出力がサポートされる。同時にscanf関数ファミリやstrtoT関数では0bプレフィクス付き文字列入力がサポートされる。*1

// C2x
#include <stdio.h>

printf("%08b", 42u);
// "00101010"

C++言語

C++20で追加されたstd::formatはバイナリ書式化指定子bをサポート済み。

// C++20
#include <format>
#include <iostream>

std::cout << std::format("{:08b}", 42u);
// "00101010"

C++17以前はstd::bitsetで手軽に済ませるか、書式化処理を自作する必要あり。

#include <bitset>
std::cout << std::bitset<8>(42u);

関連URL

*1:提案文書N2630によれば、C2xでの2進数リテラルのサポート(→id:yohhoy:20210228)に合わせた機能追加とのこと。もっと早くからサポートしてくれ。

自己再帰するラムダ式 @ C++23

次期C++2b(C++23)言語仕様に追加される Deducing this により、自己再帰するラムダ式を自然に記述できるようになる。

ラムダ式の第1引数型this autoで宣言されるselfは explicit object parameter と呼ばれ、ここではラムダ式自身のクロージャ型(closure type)へ推論される。パラメータ名は任意だが、PythonやRustなど他言語の流儀にならった方が無難。

// C++2b(P0847R7より引用)
auto fact = [](this auto self, int n) -> int {
  return (n <= 1) ? 1 : n * self(n-1);
};
std::cout << fact(5);  // "120"

C++14/17/20

C++14/17/20ではジェネリックラムダ式を利用してラムダ式の自己再帰を実装する*1。呼び出し時にはクロージャオブジェクト自身を追加で指定(下記コードでは第1引数)する必要がある。

auto fact = [](auto self, int n) -> int {
  return (n <= 1) ? 1 : n * self(self, n-1);
};
std::cout << fact(fact, 5);  // "120"

不動点コンビネータfix*2を利用すると、ラムダ式への追加の引数指定が不要になる。そこまでして実装したいかはともかく。

template<typename F>
struct fix {
  F f;
  template<typename... Args>
  decltype(auto) operator()(Args&&... args) const&
    { return f(std::ref(*this), std::forward<Args>(args)...); }
};
// テンプレート推論ガイド(deduction guide)
template<typename F> fix(F) -> fix<F>;  // C++20以降は明示不要

auto fact = fix{[](auto self, int n) -> int {
  return (n <= 1) ? 1 : n * self(n-1);
}};
std::cout << fact(5);  // "120"

// C++14では型推論のためヘルパ関数 make_fix を経由する
// template<typename F> fix<F> make_fix(F f) { return {f}; }
// auto fact = make_fix([](auto self, int n) -> int { ... });

C++11

ラムダ式が導入されたC++11時点ではstd::function利用しか実現手段がない。動的メモリ確保(→id:yohhoy:20201111)や型消去(type erasure)による実行時オーバーヘッドが生じるため、実用性のほどは微妙か。素直に関数定義したほうがマシ。

#include <functional>

std::function<int(int)> fact = [&](int n) -> int {
  return (n <= 1) ? 1 : n * fact(n-1);
};
std::cout << fact(5);  // "120"

関連URL

OpenMP 最長コンストラクト

OpenMPの最長コンストラク*1 Target Teams Distribute Parallel Worksharing-Loop SIMD Construct。

// OpenMP 4.0 for C/C++
#pragma omp target teams distribute parallel for simd
  /*for-loops*/

関連URL

*1:オプショナルな指示節(clause)を除いたプラグマ指定キーワード個数で計上。

typeof null == 'object'

バグは夜更け過ぎに仕様に変わるだろう*1 -- 詠人知らず

// This stands since the beginning of JavaScript
typeof null === 'object';

In the first implementation of JavaScript, JavaScript values were represented as a type tag and a value. The type tag for objects was 0. null was represented as the NULL pointer (0x00 in most platforms). Consequently, null had 0 as type tag, hence the typeof return value "object".

A fix was proposed for ECMAScript (via an opt-in), but was rejected. It would have resulted in typeof null === 'null'.

typeof null - JavaScript | MDN

関連URL

signatureの定義 @ C++20

C++20以降での関数シグネチャ(signature)の定義について。Concepts導入によりテンプレート制約も追加で考慮される。

まとめ:

  • 関数:名前、引数型リスト、所属する名前空間(戻り値型は除外)
  • 関数テンプレート:名前、引数型リスト、戻り値型、所属する名前空間、テンプレートパラメータリスト、requires節
  • メンバ関数:名前、引数型リスト、所属するクラス+cv修飾+参照修飾、後置requires節*1(戻り値型は除外)
  • メンバ関数テンプレート:名前、引数型リスト、戻り値型、所属するクラス+cv修飾+参照修飾、テンプレートパラメータリスト、requires節


前掲リスト中の下線部はC++20における変更箇所。C++17以前のシグネチャ定義は下記の通り。

  • 関数:名前、引数型リスト、所属する名前空間(戻り値型は除外)
  • 関数テンプレート:名前、引数型リスト、戻り値型、所属する名前空間、テンプレートパラメータリスト
  • メンバ関数:名前、引数型リスト、所属するクラス+cv修飾+参照修飾(戻り値型は除外)
  • メンバ関数テンプレート:名前、引数型リスト、戻り値型、所属するクラス+cv修飾+参照修飾、テンプレートパラメータリスト
signatureの定義 - yohhoyの日記

C++20 3.20-27より引用。構文要素template-headはテンプレートパラメータリスト(template-parameter-list)とrequires節(requires-clause)からなる。

〈function〉 name, parameter-type-list, and enclosing namespace (if any)
[Note 1: Signatures are used as a basis for name mangling and linking. -- end note]

〈non-template friend function with trailing requires-clause〉 name, parameter-type-list, enclosing class, and trailing requires-clause

〈function template〉 name, parameter-type-list, enclosing namespace (if any), return type, template-head, and trailing requires-clause (if any)

⟨friend function template with constraint involving enclosing template parameters〉 name, parameter-type-list, return type, enclosing class, template-head, and trailing requires-clause (if any)

〈class member function〉 name, parameter-type-list, class of which the function is a member, cv-qualifiers (if any), ref-qualifier (if any), and trailing requires-clause (if any)

〈class member function template〉 name, parameter-type-list, class of which the function is a member, cv-qualifiers (if any), ref-qualifier (if any), return type (if any), template-head, and trailing requires-clause (if any)

〈class member function template specialization〉 signature of the member function template of which it is a specialization and its template arguments (whether explicitly specified or deduced)

関連URL

*1:クラステンプレートに属する非テンプレートなメンバ関数は、後置requires節(trailing requires-clause)をもつ可能性がある。

std::coutの実体はどこ?

C++標準ライブラリで定義されるグローバルオブジェクトstd::cout等の宣言と定義についてメモ。

GCC/libstdc++およびClang/libcxxライブラリ実装では、宣言型(std::ostream)と実体定義の型(char[])を意図的に変えている。型の不一致は厳密にはC++仕様違反*1だが、標準ヘッダ<iostream>提供グローバルオブジェクトの生存期間要件を満たすためのHACKと考えられる。たぶん。

C++03 27.3/p2より引用(下線部は強調)。

Mixing operations on corresponding wide- and narrow-character streams follows the same semantics as mixing such operations on FILEs, as specified in Amendment 1 of the ISO C standard. The objects are constructed, and the associations are established at some time prior to or during first time an object of class ios_base::Init is constructed, and in any case before the body of main begins execution.264) The objects are not destroyed during program execution.265)
脚注264) If it is possible for them to do so, implementations are encouraged to initialize the objects earlier than required.
脚注265) Constructors and destructors for static objects can access these objects to read input from stdin or write output to stdout or stderr.

GCC/libstdc++

宣言:https://github.com/gcc-mirror/gcc/blob/releases/gcc-11.1.0/libstdc++-v3/include/std/iostream#L61

extern ostream cout;  /// Linked to standard output

実装:https://github.com/gcc-mirror/gcc/blob/releases/gcc-11.1.0/libstdc++-v3/src/c++98/globals_io.cc#L50-L59

// Standard stream objects.
// NB: Iff <iostream> is included, these definitions become wonky.
typedef char fake_ostream[sizeof(ostream)]
  __attribute__ ((aligned(__alignof__(ostream))));
fake_ostream cout;

Clang/libcxx

宣言:https://github.com/llvm/llvm-project/blob/llvmorg-12.0.1/libcxx/include/iostream#L53

extern ostream cout;

実装:https://github.com/llvm/llvm-project/blob/llvmorg-12.0.1/libcxx/src/iostream.cpp#L38-L42

alignas(ostream) char cout[sizeof(ostream)];

可読性のため各種マクロは展開済み。

MSVC

宣言:https://github.com/microsoft/STL/blob/d5feb03f320ccaa8e214e3010c40c9c5afd686e2/stl/inc/iostream#L40

extern ostream cout;

実装:https://github.com/microsoft/STL/blob/d5feb03f320ccaa8e214e3010c40c9c5afd686e2/stl/src/cout.cpp#L10-L22

#pragma init_seg(compiler)

static filebuf fout(__acrt_iob_func(1));
extern ostream cout(&fout);

可読性のため各種マクロは展開済み。init_seg(compiler)初期化順制御ディレクティブ__acrt_iob_func関数はCランタイム(CRT)の内部関数であり実装非公開。

関連URL

*1:C++03 3.5/p10: "(snip), the types specified by all declarations referring to a given object or function shall be identical, except that declarations for an array object (snip). A violation of this rule on type identity does not require a diagnostic."