重载函数作为可变参数模板函数的参数

时间:2012-01-29 16:28:33

标签: c++ c++11 variadic-functions variadic-templates

我正在尝试创建可变参数模板函数,它将参数重载函数及其参数作为参数:)

int sumall(int a) { return a; }
int sumall(int a, int b) { return a+b; }

template<typename R, typename... A>
R doit( R(*f)(A...), A... a) {
    return f(a...); }

我想在没有任何模板说明符的情况下调用doit,也不想投射:

cout << doit(sumall, 7, 6) << endl

那不能编译,但是当返回类型无效时,一切都很完美:

void printsum(int a) { cout << a << endl; }
void printsum(int a, int b) { cout << a+b << endl; }

template<typename... A>
void vdoit( void(*f)(A...), A... a) {
    f(a...); }

// ...
vdoit(printsum, 7, 6);

是否可以修改第一个模板以仅使用modyfing doit模板(我想保留sumall个函数和doit调用)?我认为可以删除typename R而只留下template<typename... A>,因为R取决于A...f,但我不知道如何表明依赖。

2 个答案:

答案 0 :(得分:7)

当获取函数的指针时,编译器需要知道您要使用哪个重载。没有办法将指针传递给“重载集”并让编译器稍后决定。你们两个例子都不适用于我尝试的任何编译器(最新版本的EDG,gcc和clang)。

我不认为你可以做你想做的事而不改变你的电话记号。如果您愿意更改呼叫,可以将有关要调用的函数的知识封装到类中,例如:

struct sumall_t {
    template <typename... T>
    auto operator()(T... args) -> decltype(sumall(args...)) {
        return sumall(args...);
    }
};

这有效地为重载集创建了一个包装器。由于结果类型无法直接推导出来,并且可能取决于函数的调用方式,因此您还需要使用不同版本的doit()

template<typename Func, typename... A>
auto doit( Func f, A... a) ->decltype(f(a...)) {
    return f(a...);
}

然后使用这样的东西:

doit(sumall_t(), 1, 2);

解决这个问题的另一种方法是强制要求结果类型的规范:在某种程度上,你尝试同时做两件事:你想推导出要调用的函数的结果类型,并且你想引导编译器选择结果集的特定重载。但是,这些是相互依存的。如果从函数指针中删除对推导任何模板的任何依赖性,则不需要包装重载集,因为您可以确定从函数的第一个参数中选择重载函数。如果您声称“如果返回类型不是void,我的编译器可以执行此操作”我会说您的编译器在执行此操作时实际上是错误的。

答案 1 :(得分:2)

(如果您已准备好使用可变参数,请滚动到此答案的结尾,以查看更好的答案,使所有内容完全变量。但我认为可变参数只是一个g ++扩展。)

如果您准备将函数名称放在参数列表的末尾,则可以使其正常工作。通过稍后放置,编译器可以从早期参数中推导出必要的类型doit

cout << doit(7, 6, sumall) << endl;
cout << doit(10, sumall) << endl;

这是demo on ideone

缺点是你必须为每个参数数量实现一个doit。我只针对单参数和双参数函数实现了它,但扩展它不应该是一个问题:

int sumall(int a) { return a; }
int sumall(int a, int b) { return a+b; }

template<typename A1, typename A2, typename R>
auto doit( A1 a1, A2 a2, R (*f) (A1,A2)) -> R {
    return f(a1, a2);
}
template<typename A1, typename R>
auto doit( A1 a1, R (*f) (A1)) -> R {
    return f(a1);
}

更新:有时,您可能会看到f作为第一个参数。但这并不像把它放在最后那样强大。考虑一个示例,其中两个函数在哪里采用相同数量的参数,但参数类型不同。 e.g:

int sumall(int a, int b) { return a+b; }
string sumall(string a, string b) { return a+" "+b; }

您需要将函数作为最后一个参数,以便模板推导可以使用开头的参数类型和数量来推断参数的类型。这是关于function-arg firstfunction-arg last的想法的演示。

将arg放在最后的唯一缺点是我们无法使用可变参数模板 - 可变参数arg包必须在最后。而且您必须完全正确地使用这些类型 - 请参阅我必须使用string("hi")而不仅仅是"hi"

使用variadic 拥有最好的世界

通过将doit实现为宏,并使用可变参数宏(gcc / g ++扩展),可以使用首先出现函数名的完全可变方程解决方案。关于ideone的演示。

cout << doit(sumall, 7, 6) << endl;
cout << doit(sumall, 10) << endl;
cout << doit(sumall, string("hi"), string("world")) << endl;

通过使用decltype和其他几个简单的类,我们可以使用提供的args来推断args的类型,然后它可以使用它来从重载集中选择正确的方法并推导出返回从中输入。

template<typename ...Args>
struct OverloadResolved {
        template<typename R>
        static auto static_doit( R (*f) (Args...), Args ... args ) -> R {
                return f(args...);
        }
};

template<typename ...Args>
auto deduce(Args...) -> OverloadResolved<Args...> {
        return OverloadResolved<Args...>();
}

template<typename T>
struct dummy : public T { };

#define doit(f, ...) ( dummy<decltype(deduce( __VA_ARGS__ ))> :: static_doit(f, __VA_ARGS__) )

我非常确定这是对宏的安全使用,不会对任何内容进行两次评估(decltype内部没有任何内容实际执行。