具有正确的重载集的调用多个函数的对象

时间:2018-10-29 21:54:02

标签: 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;
}

2 个答案:

答案 0 :(得分:3)

因为您有一个直接的参数,一个函数映射,所以您真正需要的只是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友好的维护引用的解决方案,只需要额外加上 fold-expression

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

答案 1 :(得分:0)

您必须使用auto f() -> decltype(RET) { return RET; }的方式(可以使用宏来避开样板:-/,我们也可以处理noexcept)。

类似的东西:

template <class... Fs>
struct merge_call_object {
private:
  std::tuple<Fs...> fs;
private:

    template <std::size_t... Is, typename Tuple>
    auto call(std::index_sequence<Is...>, Tuple&& tuple)
    -> decltype(std::make_tuple(std::get<Is>(fs)(std::get<Is>(std::forward<Tuple>(tuple)))...))
    {
        return std::make_tuple(std::get<Is>(fs)(std::get<Is>(std::forward<Tuple>(tuple)))...);
    }      
public:
  merge_call_object(Fs... _fs)
      : fs(std::move(_fs)...) {} 

  template <class... Args>
  auto operator()(Args &&... args)
  -> decltype(call(std::index_sequence_for<Fs...>(), std::forward_as_tuple(std::forward<Args>(args)...)))
  {
    return call(std::index_sequence_for<Fs...>(), std::forward_as_tuple(std::forward<Args>(args)...));
  }

};

Demo