LoginSignup
3
3

More than 5 years have passed since last update.

引数の数が未知の関数に、自動で引数を付けて呼び出す。

Last updated at Posted at 2014-10-20
#include <iostream>

int func3(int n1, int n2, int n3) {
    std::cout << n1 << '+' << n2 << '+' << n3 << '=' << std::flush;
    return n1+n2+n3;
}
int func2(int n1, int n2) {
    std::cout << n1 << '+' << n2 << '=' << std::flush;
    return n1+n2;
}
int func1(int n1) {
    std::cout << n1 << '=' << std::flush;
    return n1;
}
int func0(void) {
    return 0;
}

int main(void){
    std::cout << func3(1,2,3) << std::endl;
    std::cout << func2(1,2) << std::endl;
    std::cout << func1(1) << std::endl;
    std::cout << func0() << std::endl;
    return 0;
}

実行すると、

1+2+3=6
1+2=3
1=1
0

こんな感じ。

このmain()関数の部分が、書くのが面倒くさいので、

int main(void){
    std::cout << stepargs(func3) << std::endl;
    std::cout << stepargs(func2) << std::endl;
    std::cout << stepargs(func1) << std::endl;
    std::cout << stepargs(func0) << std::endl;
    return 0;
}

とだけ書いたら、自動的に連番整数を引数として関数を実行してほしい、というのがお題。

#include <type_traits>

template<int N, typename Func, typename ...Args>
auto add_steparg(typename std::enable_if<!N, Func>::type func, Args... args) {
    return func(args...);
}

template<int N, typename Func, typename ...Args>
auto add_steparg(typename std::enable_if<N, Func>::type func, Args... args) {
    return add_steparg<N-1, Func>(func, N, args...);
}

template<typename Ret, typename ...Args>
Ret stepargs(Ret (*func)(Args...)) {
    return add_steparg<sizeof...(Args), decltype(func)>(func);
}

これをmain()関数の前に書いてやればOK。

ここでは関数呼び出しのために、再帰でテンプレート引数を伸ばしていっている。
伸ばす長さの目印として、template<int N>sizeof...(Args)を最初に渡しておいて、再帰の度に減らして、ゼロになったら目的の関数を呼び出す。

しかし可変長テンプレート+可変長引数とstd::enable_ifとの相性悪い・・・。
テンプレート引数や、関数引数の最後に、std::enable_ifを使ったデフォルト値付きのダミーの引数を付けられないので、無理矢理どこかに押し込まないといけない。

#include <type_traits>

template<int N, typename Func, typename ...Args>
auto add_steparg(typename std::enable_if<!N>::type*, Func func, Args... args) {
    return func(args...);
}

template<int N, typename Func, typename ...Args>
auto add_steparg(typename std::enable_if<N>::type*, Func func, Args... args) {
    return add_steparg<N-1>(0, func, N, args...);
}

template<typename Ret, typename ...Args>
Ret stepargs(Ret (*func)(Args...)) {
    return add_steparg<sizeof...(Args)>(0, func);
}

関数引数の最初にデフォルト値なしのダミー引数を付けた方がシンプルかな。
当然呼び出し元にもダミーの引数を付けないといけないけれど。
その代わりに、テンプレート引数にFuncを付けなくても推論してくれるようになった。

template<int N> struct add_steparg{
    template<typename Func, typename ...Args>
    static auto x(Func func, Args... args) {
        return add_steparg<N-1>::x(func, N, args...);
    }
};

template<> struct add_steparg<0>{
    template<typename Func, typename ...Args>
    static auto x(Func func, Args... args) {
        return func(args...);
    }
};

template<typename Ret, typename ...Args>
Ret stepargs(Ret (*func)(Args...)) {
    return add_steparg<sizeof...(Args)>::x(func);
}

std::enable_ifを使わずに、テンプレート特殊化でやった場合。structがちょっと鬱陶しい。

ちなみに最適化MAXでコンパイルすると、単なる定数引数での関数呼び出しと同じになりました。なので実行時のオーバヘッドはゼロ。GCCすごい。

追記

if constexprが使えるなら、下記でも大丈夫の模様。

template<int N, typename Ret, typename Func, typename ...Args>
Ret add_steparg(Func func, Args... args) {
    if constexpr(N) {
        return add_steparg<N-1, Ret>(func, N, args...);
    } else {
        return func(args...);
    }
}

template<typename Ret, typename ...Args>
Ret stepargs(Ret (*func)(Args...)) {
    return add_steparg<sizeof...(Args), Ret>(func);
}
3
3
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3