Объект, вызывающий несколько функций с правильным набором перегрузки - PullRequest
0 голосов
/ 30 октября 2018

Для набора 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 Перегрузка: проклятие всех функций высшего порядка , около 6:40 он говорит о том, как обернуть функцию в лямбду.


Моя реализация без правильного набора перегрузки, плюс небольшой тест на идеальную пересылку.

#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 голосов
/ 30 октября 2018

Вы должны использовать 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)...));
  }

};

Демо

0 голосов
/ 30 октября 2018

Поскольку у вас есть прямой один аргумент, одно отображение функции, все, что вам действительно нужно, это 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);
}
...