yohhoyの日記

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

... 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:グイド・ヴァンロッサム

オーバーロード関数とテンプレート引数推論

プログラミング言語C++において、オーバーロードされた関数群をテンプレートパラメータへ引き渡す方法。

テンプレート引数の関数型を一意に推論できないため、ジェネリックラムダ式でラップする必要がある。テンプレート引数Fラムダ式に対応する固有のクロージャ型へと推論され、operator()演算子の呼び出しまでオーバーロード解決が遅延される。

#include <utility>

// オーバーロードされた関数f
void f(int);
void f(int, int);

// 1引数バージョンを呼び出す
template <class F>
void call1(F f)
  requires requires { f(0); }
{ f(42); }

// 2引数バージョンを呼び出す
template <class F>
void call2(F f)
  requires requires { f(0, 0); }
{ f(1, 2); }

int main()
{
  // NG: ill-formed
  call1(f); 
  call2(f);
  // GCC: no matching function for call to 'callN(<unresolved overloaded function type>)'
  //   couldn't deduce template parameter 'F'
  // Clang: no matching function for call to 'call1N'
  //   couldn't infer template argument 'F'
  // MSVC: no matching overloaded function found
  //   could not deduce template argument for 'F'

  // OK: ジェネリックラムダ式
  auto g = []<class... Ts>(Ts&&... args)
    { return f(std::forward<Ts>(args)...); };
  call1(g);
  call2(g);
}

C++20機能を使えない場合のフォールバック実装:

// C++14/17: SFINAE版
template <class F>
auto call1(F f)
  -> decltype(f(0), void())
{ f(42); }

template <class F>
auto call2(F f)
  -> decltype(f(0, 0), void())
{ f(1, 2); }

auto g = [](auto&&... args)
  { return f(std::forward<decltype(args)>(args)...); };

関連URL

R.I.P. "= {0}"

プログラミング言語Cの次期C2x(C23)言語仕様では、空のブレース{}を用いた配列・構造体の初期化が許容される。C++では当初からOK。

typedef struct S { int m1, m2; } S;

// C17:ill-formed / C2x:OK / C++:OK
int arr0[10] = {};
S obj0 = {};

// C:OK / C++:OK
int arr1[10] = { 0 };  
S obj1 = { 0 };

C17現在では空の初期化子リストは許されず、少なくとも1つの初期化子を記述する(= {0})必要あり。対応する初期化子のない配列要素や構造体メンバは値0で初期化される(→id:yohhoy:20170510)。

仕様上はNGだが初期化= {}はあまりに多用されるテクニックのため、GCCやClangなどは独自拡張として許容している。MSVCではC17標準通りコンパイルエラーとなる。

gcc 12.1.0/-pedantic指定時の警告メッセージ:

warning: ISO C forbids empty initializer braces [-Wpedantic]

clang 14.0.0/-pedantic指定時の警告メッセージ:

warning: use of GNU empty initializer extension [-Wgnu-empty-initializer]

関連URL

#embedディレクティブ

プログラミング言語Cの次期C2x(C23)言語仕様に追加される#embedディレクティブについて。外部ファイルをバイナリデータとしてプログラムに埋込む機能。

下記コードは、外部PNGファイル内容を生成プログラム中のuint8_t型配列として埋め込む例*1 *2。C17現在は外部ツール*3を用いたビルドステップで対応しているものが、C2x以降はC言語処理系(プリプロセッサ)のみで実現される。

// C2x
#include <stdint.h>  // uint8_t

#if __has_embed("resource/icon.png")
constexpr uint8_t icon[] = {
#embed "resource/icon.png"
};
static_assert(
  (icon[0]==137 && icon[1]=='P' && icon[2]=='N' && icon[3]=='G'
   && icon[4]==13 && icon[5]==10 && icon[6]==26 && icon[7]==10),
  "invalid PNG format");
// https://www.w3.org/TR/PNG-Structure.html
#else
#error "icon resource not found!"
#endif

// icon[] == PNG圧縮画像データ列

まとめ:

  • #emdedディレクティブ
    • 指定されたファイル(<path>"path"またはマクロ置換結果)の中身を読み取り、コンマ区切りの整数定数リストへと展開する。
    • C2x標準は下記パラメータ4種類の追加指定をサポートし、また処理系定義(implementation-defined)のパラメータ定義を許容する。
    • limit(N):展開されるバイナリデータをNバイト以下に制限する。
    • prefix(tokens):展開後リストの直前にtokensを配置する。バイナリデータ長=0の場合は何もしない。
    • suffix(tokens):展開後リストの直後にtokensを配置する。バイナリデータ長=0の場合は何もしない。
    • if_empty(tokens):バイナリデータ長=0の場合にtokensを代替配置する。それ以外は何もしない。
  • __has_embed
    • 指定ファイルの有無、空(empty)のバイナリデータを判定するプリプロセッサ式。
    • 0:指定ファイルが見つからない。
    • 1:指定ファイルが存在し、バイナリデータは空ではない。
    • 2:指定ファイルが存在し、バイナリデータは空(サイズ0)。
  • おまけ:C++標準に対してもP1967が提案されている。2022年9月現在の検討状況より、C++2c(C++26)以降での導入が想定される。

ホストプログラム中に外部ファイルに記述されたGLSLシェーダプログラムを埋め込む例(提案文書N3017より改変引用):

#define SHADER_TARGET "ches.glsl"
extern char* merp;

void init_data () {
  const char whl[] = {
#embed SHADER_TARGET \
    prefix(0xEF, 0xBB, 0xBF, ) /* UTF-8 BOM */ \
    suffix(,)
    0
  };
  // always null terminated,
  // contains BOM if not-empty
  int is_good = (sizeof(whl) == 1 && whl[0] == '\0')
    || (whl[0] == '\xEF' && whl[1] == '\xBB'
      && whl[2] == '\xBF' && whl[sizeof(whl) - 1] == '\0');
  assert(is_good);
  strcpy(merp, whl);
}
#define SHADER_TARGET "edith-impl.glsl"
extern char* null_term_shader_data;

void fill_in_data () {
  const char internal_data[] = {
#embed SHADER_TARGET  \
    suffix(, 0) \
    if_empty(0)
  };
  strcpy(null_term_shader_data, internal_data);
}

関連URL

*1:コンパイル時constexpr定数は提案文書 N3018 にてC2x導入予定。コンパイル時constexpr関数をもつC++とは異なり、C2x時点ではconstexpr定数のみが導入される。

*2:C2x仕様には(PDF)N2934が採択され、C++言語と同じ static_assert キーワードが導入される。一方で、C11からの _Static_assert は廃止予定の機能(obsolescent feature)となる。また例示コードでは利用していないが、(PDF)N2265の採択により理由文字列もC2xから省略可能となる。C++17以降の static_assert と同等。

*3:例えば https://linux.die.net/man/1/xxd などでバイナリファイルをC言語用の変数定義へと変換可能。

typeof演算子 in 標準C

プログラミング言語Cの次期仕様C2x(C23)では、式から型情報を取り出す typeof演算子(typeof operator) が追加される。

// C2x
const int x = /*...*/;
typeof(x) y;  // const int型
typeof_unqual(x) z;  // int型

int func(int);
typeof(func)* pf;  // int(*)(int)型

まとめ:

  • 2種類のtypeof演算子が追加される。
    • typeofオペランドの型情報をそのまま返す。従来からある同名のGCC拡張機能を標準化したもの。
    • typeof_unqualオペランドから型修飾(type qualifiers)を除去*1した型情報を返す。
    • C2x typeof_unqual(E)C++ std::remove_cvref_t<decltype(E)>*2
  • typeof演算子は 式(expression) または 型名(type-name) を対象とする。
    • sizeof演算子とは異なり*3、typeof演算子に式を指定する場合も括弧は必須。
  • C言語typeof != C++言語のdecltype
    • C2x typeofは型名も指定可能/C++decltypeは式のみサポート。
    • C言語には参照型(reference type)が存在しないため、オペランドの括弧有無により両者の挙動が異なる。

typeof(C) vs. decltype(C++)

C言語typeofC++言語のdecltype(→id:yohhoy:20200817)とで導出される型が異なる例:

int x;  // int

// C2x
typeof( x ) y0;  // int
typeof((x)) y1;  // int

// C++
decltype( x ) z0;  // int
decltype((x)) z1;  // int&(参照型)

C++11 decltype検討時の提案文書N1607*4でもGCC拡張機能typeofにおける参照型の扱いを取り上げ、それとは異なる動きをするキーワードとしてC++へ導入した経緯がある。

3 Design alternatives for typeof
Two main options for the semantics of a typeof operator have been discussed: either to preserve or to drop references in types.
(snip)
A reference-dropping typeof always removes top-level references. Some compiler vendors (EDG, Metrowerks, GCC) provide a typeof operator as an extension with reference-dropping semantics. This appears to be a reasonable semantics for expressing the type of variables. On the other hand, the reference-dropping semantics fails to provide a mechanism for exactly expressing the return types of generic functions, as demonstrated by Stroustrup. This implies that a reference-dropping typeof would cause problems for writers of generic libraries.
(snip)
Therefore, we propose that the operator be named decltype.

関連URL

*1:トップレベルの const, volatile, restrict(→id:yohhoy:20120223), _Atomic の4種類全てが除去される。C言語の _Atomic キーワードには、指定子 _Atomic(T) と 修飾子 _Atomic T の2種類の用法があるが、typeof_unqual は両者を区別せずに除去する。

*2:C++言語仕様には restrict 修飾子は存在しない。またC++のatomic変数はクラステンプレート std::atomic<T> として定義され、C言語のような _Atomic キーワードが存在しない。

*3:https://qiita.com/yohhoy/items/a2ab2900a2bd36c31879

*4:https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2004/n1607.pdf

reproducible/unsequenced属性

プログラミング言語Cの次期C2x(C23)言語仕様に追加される属性(→id:yohhoy:20200505reproducible, unsequencedに関するメモ。関数呼び出しに対する最適化ヒント。

// C2x
int calc(int x, int y) [[unsequenced]];

int a = /*...*/;
int b = calc(a) * 2;
/* 任意の処理 */
int c = calc(a) * 4;
// calc関数に付与されたunsequenced属性により、
// コンパイラによる下記の最適化が許可される。
// int b = calc(a) * 2;
// int c = b * 2;
// /* 任意の処理 */

まとめ:

  • 関数または関数ポインタに対して付与する属性。
  • reproducible属性=副作用を持たない(effectless)+連続呼出しは同一結果(idempotent)
  • unsequenced属性=reproducible属性+可変状態を持たない(stateless)+引数以外に依存しない(independent)
  • 関数動作セマンティクスが指定属性に反する場合は未定義動作(undefined behavior)となる。
    • Cコンパイラは関数実装が指定属性を満たすことを検査しなくてもよいが、できる限り診断メッセージを出すことが推奨される(recommended practice)。
  • GCC拡張属性__attribute__((pure))reproducibleGCC拡張属性__attribute__((const))unsequencedと概ね近似できる。
  • 注意:unsequenced属性の関数は再入可能(reentrant)や並行実行(executed concurrently)に対する安全性を直接意味しない。*1

例えば標準ヘッダ<math.h>提供の数学関数群はエラー発生時にerrnoへ値を書込む可能性があるため、一般にreproducibleunsequencedいずれでもない。実引数に対して制約条件を保証できるのであれば、プログラマの責任で同属性を付与した関数再宣言を行ってもよい。提案文書N2956よりコード例を引用。*2 *3

// C2x
#include <math.h>
#include <fenv.h>

inline double distance (double const x[static 2]) [[reproducible]] {
  #pragma FP_CONTRACT OFF
  #pragma FENV_ROUND FE_TONEAREST
  // We assert that sqrt will not be called with invalid arguments
  // and the result only depends on the argument value.
  extern typeof(sqrt) [[unsequenced]] sqrt;
  return sqrt(x[0]*x[0] + x[1]*x[1]);
}

double g (double y[static 1], double const x[static 2]) {
  // We assert that distance will not see different states of the floating
  // point environment.
  extern double distance (double const x[static 2]) [[unsequenced]];
  y[0] = distance(x);
  ...
  return distance(x); // replacement by y[0] is valid
}

C2x WD N3047 6.7.12.7/p3, p5-8より一部引用。

3 The main purpose of the function type properties and attributes defined in this clause is to provide the translator with information about the access of objects by a function such that certain properties of function calls can be deduced; the properties distinguish read operations (stateless and independent) and write operations (effectless, idempotent and reproducible) or a combination of both (unsequenced). (snip)

5 A function definition f is stateless if any definition of an object of static or thread storage duration in f or in a function that is called by f is const but not volatile qualified.
6 (snip) A function pointer value f is independent if for any object X that is observed by some call to f through an lvalue that is not based on a parameter of the call, then all accesses to X in all calls to f during the same program execution observe the same value; otherwise if the access is based on a pointer parameter, there shall be a unique such pointer parameter P such that any access to X shall be to an lvalue that is based on P. A function definition is independent if the derived function pointer value is independent.
7 A store operation to an object X that is sequenced during a function call such that both synchronize is said to be observable if X is not local to the call, if the lifetime of X ends after the call, if the stored value is different from the value observed by the call, if any, and if it is the last value written before the termination of the call. An evaluation of a function call is effectless if any store operation that is sequenced during the call is the modification of an object that synchronizes with the call; (snip)
8 An evaluation E is idempotent if a second evaluation of E can be sequenced immediately after the original one without changing the resulting value, if any, or the observable state of the execution. A function definition is idempotent if the derived function pointer value is idempotent.
9 A function is reproducible if it is effectless and idempotent; it is unsequenced if it is stateless, effectless, idempotent and independent.

関連URL

*1:unsequencedを満たすには関数終了時点で観測可能な変化がなければよく、関数は(呼出し元がポインタ経由で渡した)静的記憶域期間(static storage duration)変数に対して読取/書込を行う可能性がある。...そんな用途にstatic変数を使うんじゃねぇ(`ェ´)

*2:関数引数リスト中の T arg[static N] はC99で導入された構文。T 型のポインタ型仮引数 arg が少なくとも N 要素を持つデータ領域を指すことを表明する。id:yohhoy:20170503 も参照のこと。

*3:typeof はC2xで導入される型情報を取り出す演算子(→id:yohhoy:20220912)。typeof(sqrt) はC標準ライブラリ関数 sqrt の型情報つまり「戻り値doubleかつ引数リスト(dubule)をもつ関数型」を表す型指定子であり、該当文は extern double sqrt(double) [[unsequenced]]; 関数宣言となる。

nullptr定数 in 標準C

プログラミング言語Cの次期仕様C2x(C23)にて、ついにC言語にも真のヌルポインタ定数nullptrがやってくる。
C++11(→id:yohhoy:20120503)から遅れること12年。

int *p1 = nullptr;  // C2x以降
int *p2 = NULL;
int *p3 = 0;

まとめ:

  • C/C++両言語のnullptrがセマンティクス一致するよう設計されている。
  • 事前定義定数*1nullptrは、専用のnullptr_t型が取りうる唯一の値。
    • 定数nullptrはいつでも利用可能(ヘッダinclude不要)。
    • nullptr_t型は標準ヘッダ <stddef.h> で定義される。*2
  • C2xで追加されるnullptrのほか、現行の整数定数値0やマクロNULLをいずれもヌルポインタ定数(null pointer constant)として扱う。
  • nullptr_t型はvoid*型と同じサイズ/アライメントを持ち、nullptr(void*)0の内部表現は等しい。
    • 可変引数リストに対する番兵(sentinel)実引数として、nullptrNULLよりもポータブルに使える。(→id:yohhoy:20160224

関連URL

*1:predefined constants はC2xにて新たに導入される構文要素。false, true, nulllptr の3種類が追加される。false/true については C言語のbool型とその名前について 〜もう_Boolは嫌だ〜 を参照。

*2:C2xで導入される typeof_unqual を利用して typedef typeof_unqual(nullptr) nullptr_t; と定義される。