对于一组 N
函数 fs...
每个只接受一个参数,我想创建一个对象,它有一个调用运算符接受 N
个参数 args...
,调用所有函数 fs(args)...
并以元组形式返回输出。
基本类看起来像这样。我用???
标记了我不知道如何实现的地方。
template <class... Fs>
struct merge_call_object {
merge_call_object(Fs... _fs)
: fs(std::move(_fs)...) {}
template <class... Args>
auto operator()(Args &&... args) -> decltype(???){
???
}
std::tuple<Fs...> fs;
};
这个对象的预期用途是:
auto f1 = [](double x){ return 2*s; };
auto f2 = [](std::string const& s){ return s+" World!"; };
auto mco = merge_call_object{f1,f2};
// The following should yield std::tuple{42, "Hello World!"}
auto out = mco(21, "Hello ");
到目前为止一切顺利,执行上述操作很“简单”,但我希望 mco
的重载按预期工作,即以下应该编译并通过
static_assert(std::is_invocable_v<decltype(mco), double, std::string> == true);
static_assert(std::is_invocable_v<decltype(mco), double, double> == false);
我看到的最大挑战是如何正确实现 SFINAE -> decltype(???)
。
这个问题的灵感来自 CppCon 最近的演讲 Overloading: The Bane of All Higher-Order Functions ,大约 6:40,他谈到了如何将函数包装到 lambda 中。
我的实现没有正确的重载集,加上一个完美转发的小测试。
#include <iostream>
#include <tuple>
#include <utility>
namespace detail {
template <std::size_t... I>
constexpr auto integral_sequence_impl(std::index_sequence<I...>) {
return std::make_tuple((std::integral_constant<std::size_t, I>{})...);
}
template <std::size_t N, typename Indices = std::make_index_sequence<N>>
constexpr auto integral_sequence = integral_sequence_impl(Indices{});
template <std::size_t N, typename Fun>
constexpr decltype(auto) apply_sequence(Fun &&fun) {
return std::apply(std::forward<Fun>(fun), detail::integral_sequence<N>);
}
} // namespace detail
template <class... Fs>
struct merge_call_object {
merge_call_object(Fs... _fs)
: fs(std::move(_fs)...) {}
template <class... Args>
auto operator()(Args &&... args) {
constexpr int N = sizeof...(Fs);
auto f = [&](auto I) { return std::get<I>(fs); };
auto arg = [&](auto I) -> decltype(auto) {
return std::get<I>(std::forward_as_tuple(std::forward<Args>(args)...));
};
return detail::apply_sequence<N>(
[&](auto... Is) { return std::forward_as_tuple(f(Is)(arg(Is))...); });
}
std::tuple<Fs...> fs;
};
struct Screamer {
Screamer() { std::cout << "Constructor!" << std::endl; }
Screamer(Screamer &&s) { std::cout << "Move constructor!" << std::endl; }
Screamer(Screamer const &s) { std::cout << "Copy constructor!" << std::endl; }
};
int main() {
auto f1 = [](auto &&x) -> decltype(auto) {
std::cout << "Calling f1" << std::endl;
return std::forward<decltype(x)>(x);
};
auto f2 = [](auto &&x) -> decltype(auto) {
std::cout << "Calling f2" << std::endl;
return std::forward<decltype(x)>(x);
};
auto mco = merge_call_object{f1, f2};
auto [s1, s2] = mco(Screamer{}, Screamer{});
return 0;
}
最佳答案
因为你有一个直接的参数,一个函数映射,你真正需要的是std::apply
:
template <class... Args>
auto operator()(Args&&... args) {
return std::apply([&](Fs&... fs){
return std::tuple(fs(std::forward<Args>(args))...);
}, fs);
}
这将衰减所有类型(也就是说,如果某些函数/参数对实际上返回一个 int&
,这将在那个位置给你一个 int
) .这也不适合 SFINAE。
既对 SFINAE 友好又维护引用的解决方案只需要一个额外的破折号折叠表达式:
template <class... Args,
std::enable_if_t<(std::is_invocable_v<Fs&, Args> && ...), int> = 0>
auto operator()(Args&&... args) {
return std::apply([&](Fs&... fs){
return std::tuple<std::invoke_result_t<Fs&, Args>...>(
fs(std::forward<Args>(args))...);
}, fs);
}
关于c++ - 使用正确的重载集调用多个函数的对象,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53054401/