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

发布于 2024-12-29 09:22:00 字数 936 浏览 2 评论 0 原文

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

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 来完成,因为 R 取决于 A ...f,但我不知道如何显示这种依赖性。

I'm trying to make variadic template function, which takes as arguments overloaded function and its arguments :)

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...); }

I want to call doit without any template specifiers nor casting:

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

That doesn't compile, but when return types are void, everything work perfect:

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);

Is it possible to modify first template to work with modyfing only doit template (I want to preserve sumall functions and doit call)? I think it can be done with removing typename R and leaving just template<typename... A> since R depends on A... and f, but I don't have any idea how to show that dependency.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

听风念你 2025-01-05 09:22:00

当获取函数指针时,编译器需要知道要使用哪个重载。没有办法将指针传递给“重载集”并让编译器稍后决定。你们的示例都不适用于我尝试过的任何编译器(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...);
}

然后将使用类似this:

doit(sumall_t(), 1, 2);

解决此问题的另一种方法是强制指定结果类型:以某种方式,您尝试同时做两件事:您想要推断要调用的函数的结果类型,并且您想要指导编译器进行选择结果集的特定过载。然而,这些是相互依存的。如果您删除了对从函数指针推导任何模板的任何依赖,则不需要包装重载集,因为您可以从函数的第一个参数确定重载函数的选择。如果您声称“如果返回类型不是 void,我的编译器可以做到这一点”,我会说您的编译器这样做实际上是错误的。

When taking a pointer of a function the compiler needs to know which of the overloads you want to use. There is no way to pass a pointer to an "overload set" and have the compiler decide later. Neither of you examples works with any of the compilers I tried (very recent versions of EDG, gcc, and clang).

I don't think you can do what you want without changing the notation of your call. If you are willing to change the call you can encapsulate the knowledge about the function to be called into a class, e.g.:

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

This effectively creates a wrapper for an overload set. Since the result type can't be deduced directly and may depend on how the function is called, you'd need to use a different version of doit() as well:

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

This would then be used something like this:

doit(sumall_t(), 1, 2);

Another way to fix this is to mandate specification of the result type: in some way you try to do two things at once: you want to deduce the result type of the function to be called and you want to guide the compiler to choose a specific overload of a result set. However, these are interdependent. If you remove any dependency on deducing any template from the function pointer, you don't need wrap the overload set because you can determine the choice of overloaded function from the first argument to the function. In case you claim that "my compiler can do it if the return type isn't void" I'd say that your compiler is actually wrong in doing this.

绾颜 2025-01-05 09:22:00

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

如果您准备将函数的名称放在参数列表的末尾,那么它就可以工作。通过稍后放置,编译器可以从前面的参数中推断出必要的类型 doit

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

这是一个 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 作为第一个参数。但这并不像放在最后那么强大。考虑以下示例,其中 where 是两个函数,它们采用相同数量的参数,但参数类型不同。例如:

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

您需要将函数作为最后一个参数,以便模板推导可以在开始时使用参数的类型和数量来推导参数的类型。这是关于 ideone 的 function-arg firstfunction-arg 最后

将 arg 放在末尾的唯一缺点是我们无法使用可变参数模板 - 可变参数参数包必须位于末尾。而且您必须获得完全正确的类型 - 看看我如何必须使用 string("hi") 而不是简单地使用 "hi"

使用可变参数获得最好的效果

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

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

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

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 中的任何内容都不会实际执行。

(If you're prepared to use variadic macros, then scroll to the end of this answer to see a better answer which make everything fully variadic. But I think that variadic macros are just a g++ extension.)

It can be made to work, if you're prepared to put the name of the function at the end of the parameter list. By putting it later, the compiler can deduce the necessary types from the earlier parameters to doit:

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

Here is a demo on ideone.

The downside is that you have to implement one doit for each number of parameters. I've only implemented it for one- and two- parameter functions, but it shouldn't be a problem to extend this:

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);
}

Update: Sometimes, it might appear that you can get away with having f as the first argument. But that's not as robust as putting it at the end. Consider the example where where are two functions that take the same number of arguments, but different types of parameters. e.g.:

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

You need to have the function as the last argument, in order that the template deduction can use the type and number of parameters at the start to deduce the types of the arguments. Here's a demo on ideone of function-arg first and function-arg last.

The only downside with putting the arg at the end is that we can't then use variadic templates - variadic arg packs must be at the end. And you must get the types exactly right - see how I had to use string("hi") instead of simply "hi".

Using variadic macros to have the best of all worlds

By implementing doit as a macro, and using variadic macros (a gcc/g++ extension), it is possible to have a fully variadic solution with the function name appearing first. A demo on ideone.

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

By using decltype and a couple of other simple classes, we can use the args provided to deduce the types of the args and then it can use that to select the right method from the overload set and deduce the return type from that.

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__) )

I'm pretty sure this is a safe use of macros, nothing will be evaluated twice (nothing inside decltype actually executes.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文