yohhoyの日記

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

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

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

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

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

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