本記事はai_9684_dctソロ Advent Calendar 2020 3日目の記事です。
Pythonには「デコレータ」という、関数に追加の機能を修飾するシンタックスシュガーが存在する。
def decorator_sample(func): def wrapper(*args, **kwargs): print(f'Arguments: {args}') print(f'Keyword arguments: {kwargs}') result = func(*args, **kwargs) print(f'Result: {result}') return result return wrapper @decorator_sample @decorator_sample def add_int2(a: int, b: int): return a + b add_int2(12, 41) # 出力結果 Arguments: (12, 41) Keyword arguments: {} Arguments: (12, 41) Keyword arguments: {} Result: 53 Result: 53
デコレータは、親切なライブラリだとサポートしていることが多い印象。
今回はPythonのデコレータっぽいものをC++で実装してみた。
C++17によるデコレータの実装
といってもPythonのように @decorator
とつけるだけで修飾するには、言語機能レベルでサポートしなければならない。そこまでのことは出来ないので、デコレートしたい関数オブジェクトを受け取る関数を作って、そこでデコレートするという実装にした。
今回の実装にはこちらのサイトを参考にした。
C++のPython関数デコレータに相当するものは何ですか?
#include <iostream> template <class... Ts> void print_all(std::ostream& os, const std::string& separator, Ts const&... args) { ((os << args << separator), ... ); } template <class T> auto decorator(T&& func) { auto wrapper = [func = std::forward<T>(func)](auto&&... args){ std::cout << "arguments: "; print_all(std::cout, ", " , args...); std::cout << "\n"; auto result = func(std::forward<decltype(args)>(args)...); std::cout << "Result: " << result << '\n'; return result; }; return wrapper; } int add_int2(int a, int b) { return a + b; } int main() { auto decorated = decorator(decorator(add_int2)); decorated(12, 29); } // 出力結果 // arguments: 12, 29, // arguments: 12, 29, // Result: 41 // Result: 41
[C++] gcc HEAD 11.0.0 20201106 (experimental) - Wandbox
(ちょっと)文法解説
デコレータへの関数及びその引数の受け渡しには完全転送を利用している。
template <class T> auto decorator(T&& func) { // ユニバーサル参照で仮引数を宣言して auto wrapper = [func = std::forward<T>(func)](auto&&... args){ // std::forwardで完全転送 // 以下省略 }
完全転送についてはこの辺りの資料が参考になる。
デコレートする関数に渡される実引数を列挙するための print_all
関数には、C++17以降でサポートされている畳み込み式(fold expression)を利用している。そのため、C++14以前のコンパイラでは動作しない。