Gaming Life

一日24時間、ゲームは10時間

Pythonのデコレータ(風)をC++で実装してみる

本記事は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で完全転送
// 以下省略
}

完全転送についてはこの辺りの資料が参考になる。

marycore.jp

デコレートする関数に渡される実引数を列挙するための print_all 関数には、C++17以降でサポートされている畳み込み式(fold expression)を利用している。そのため、C++14以前のコンパイラでは動作しない。

cpprefjp.github.io