parameter pack 仅作为部分参数的问题

2022-07-02 07:32:39 +08:00
 Contextualist
#include <iostream>
#include <functional>

template <typename ... Args>
int invoke(const std::function<int(Args ..., bool)>& f, Args&& ... args) {
	int r0 = f(std::forward<Args>(args)..., false);
	int r1 = f(std::forward<Args>(args)..., true);
	return r0 + r1;
}

int g(int a, int b, bool c) {
	if (c) {
		return a + b;
	}
	return a - b;
}

int main() {
	int r = invoke(std::function<int(int,int,bool)>(g), 2, 3);
	std::cout << r << std::endl;
}

上面的例子在 GCC 里类型推导不出来。如果 invokefconst std::function<int(bool, Args ...)>& 则可以推导出来,但像这种附加的参数在 Args 后面的情况怎么办呢?

1629 次点击
所在节点    C++
6 条回复
GeruzoniAnsasu
2022-07-02 07:58:46 +08:00
应该无解,不如说这看着像个缺陷,不应该合法才对

无论是可变参数 va_args 也好,还是可变模板参数 typename ... TS 也好,本来都只能作为最后一个参数。这个 case 推导不出来不奇怪,但居然没报错
geelaw
2022-07-02 08:03:08 +08:00
你可以用 SFINAE 的思路

#include<utility>
#include<functional>

template <typename TCallable, typename ...TArgs>
auto invoke_functor(TCallable &&f, TArgs&& ...args) -> std::enable_if_t<std::is_same_v<decltype(f(std::forward<TArgs>(args)..., std::declval<bool>())), int>, int>
{
int r0 = f(std::forward<TArgs>(args)..., false);
int r1 = f(std::forward<TArgs>(args)..., true);
return r0 + r1;
}

template <typename TCallable, typename ...TArgs>
auto invoke_function(const TCallable &f, TArgs&& ...args) -> std::enable_if_t<std::is_same_v<TCallable, std::function<int(TArgs..., bool)>>, int>
{
int r0 = f(std::forward<TArgs>(args)..., false);
int r1 = f(std::forward<TArgs>(args)..., true);
return r0 + r1;
}

int g(int a, int b, bool c)
{
return c ? a + b : a - b;
}

#include<iostream>

int main()
{
std::cout << invoke_functor(g, 2, 3) << std::endl;
std::cout << invoke_function(std::function<int(int, int, bool)>(g), 2, 3) << std::endl;
// Does not work.
// std::cout << invoke_function(g, 2, 3) << std::endl;
}
Contextualist
2022-07-02 08:53:50 +08:00
@GeruzoniAnsasu (我同事说 clang 可过编译🤦‍♂️)对,语法上确实说不过去,但是语义上来说,确实有这个需求。一般来说,可变参数一般用于“其余的可选参数”,但在这里我想表达更类似宏:对于一组参数,搭配不同的可选参数遍历执行。

@geelaw 确实可行,type traits 魔法,学习了
dangyuluo
2022-07-02 09:07:23 +08:00
@geelaw `invoke_function`里 SFINAE 的方法是比较两个`std::function`是否相同,请问比`invoke_functor`里检查返回值好在哪里呢。

另添加了个`std::is_invokable`的例子 https://godbolt.org/z/1jf5Ej9v4
geelaw
2022-07-02 09:14:44 +08:00
@dangyuluo 因为可调用的东西不一定是 std::function ,还可以是 lambda 表达式、函数之类的。从 lambda 表达式、函数建立 std::function 有额外的内存分配,对性能不好。此外你也看到了 invoke_function 不能接受函数的名字,必须手工构造一个 std::function 。

更好的思路是检查这个东西是否可以赋值给 int ,用

std::is_same_v<
std::declval<int &>() = f(std::forward<TArgs>(args)..., std::declval<bool>()),
int &
>

这样也可以接受返回 const int &、int &、short 等的可调用的东西。
geelaw
2022-07-02 09:20:03 +08:00
@dangyuluo #4 我在 #5 的回复里误解了你的意思,实现 invoke_function 只是为了复现楼主一开始想要实现的版本。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/863563

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX