c++ - 使用正确的重载集调用多个函数的对象

标签 c++ templates c++17 template-meta-programming

对于一组 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/

相关文章:

c++ - 延迟成员变量类型定义,直到另一个类继承它

C++ 模板特化

c++ - 根据类模板的类型参数自动化类模板的大小参数

c++ - 正则表达式不以零开头

c++ - 如何在 C++ 中使用颜色图(调色板)保存 tif

c++ - SSE 加载相邻值

c++ - 在 C++ 中标记 "Braced Initializer List"样式的字符串(使用 Boost?)

c++ - 多维数组内存存储概述

c++ - 匹配别名模板作为模板参数

c++ - 为什么这个嵌套的 lambda 不被认为是 constexpr?