yohhoyの日記

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

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

関数テンプレートの非型テンプレートパラメータ(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:グイド・ヴァンロッサム

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

プログラミング言語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}"

次期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ディレクティブ

次期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 operaor) が追加される。

// 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++
declype( x ) z0;  // int
declype((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