Подтвердить что ты не робот

Можно ли вернуть вариационную лямбду из шаблона функции?

У меня есть следующий фрагмент кода (С++ 11):

template <typename F,
          typename FirstT,
          typename... FIn>
auto min_on(F f, FirstT first, FIn... v) -> typename std::common_type<FirstT, FIn...>::type
{
  using rettype = typename std::common_type<FirstT, FIn...>::type;
  using f_rettype = decltype(f(first));

  rettype result = first;
  f_rettype result_trans = f(first);
  f_rettype v_trans;
  (void)std::initializer_list<int>{
      ((v_trans = f(v), v_trans < result_trans)
           ? (result = static_cast<rettype>(v), result_trans = v_trans, 0)
           : 0)...};
  return result;
}

Что в принципе возвращает аргумент result, который дал минимальное значение для выражения f(result). Это можно сделать так:

auto mod7 = [](int x)
{
    return x % 7;
};

auto minimum = min_on(mod7, 2, 8, 17, 5);
assert( minimum == 8); // since 8%7 = 1 -> minimum value for all arguments passed

Теперь я хотел бы использовать это в "curried", чтобы я мог получить вариационную лямбду от min_on, а затем вызвать ее с помощью аргументов (которые я могу получить позже), например:

auto mod7 = [](int x)
{
    return x % 7;
};

auto f_min = min_on(mod7);
auto minimum = f_min(2, 8, 17, 5);
// or 
auto minimum = min_on(mod7)(2, 8, 17, 5);

Возможно ли это?

4b9b3361

Ответ 1

В С++ 11 работает следующее, если вы хотите вручную создать объект функции:

template <typename F>
struct min_on_t {
    min_on_t(F f) : f(f) {}

    template <typename T, typename... Ts>
    auto operator ()(T x, Ts... xs) -> typename std::common_type<T, Ts...>::type
    {
        // Magic happens here.
        return f(x);
    }

    private: F f;
};

template <typename F>
auto min_on(F f) -> min_on_t<F>
{
    return min_on_t<F>{f};
}

И затем назовите его:

auto minimum = min_on(mod7)(2, 8, 17, 5);

Чтобы использовать lambdas в С++ 14, вам нужно опустить возвращаемый тип возврата, потому что вы не можете указать тип лямбда, не назначая сначала переменную, потому что лямбда выражение не может иметь место в неоценимом контексте.

template <typename F>
auto min_on(F f)
{
    return [f](auto x, auto... xs) {
        using rettype = std::common_type_t<decltype(x), decltype(xs)...>;
        using f_rettype = decltype(f(x));

        rettype result = x;
        f_rettype result_trans = f(x);
        (void)std::initializer_list<int>{
          (f(xs) < result_trans
               ? (result = static_cast<rettype>(xs), result_trans = f(xs), 0)
               : 0)...};
        return result;
    };
}

Ответ 2

Не уверен на С++ 11, но в С++ 14 вы можете создать лямбда, чтобы обернуть свою функцию в:

auto min_on_t = [](auto f) {
    return [=](auto ... params) {
        return min_on(f, params...);
    };
};

auto min_t = min_on_t(mod7);
auto minimum = min_t(2, 8, 17, 5);

Live on Coliru

Ответ 3

В С++ 14 это легко.

template<class F>
auto min_on( F&& f ) {
  return [f=std::forward<F>(f)](auto&& arg0, auto&&...args) {
    // call your function here, using decltype(args)(args) to perfect forward
  };
}

Многие компиляторы получили auto вывод возвращаемого типа и аргументы в lambdas, работающие до полной поддержки С++ 14. Таким образом, номинальный компилятор С++ 11 может скомпилировать это:

auto min_on = [](auto&& f) {
  return [f=decltype(f)(f)](auto&& arg0, auto&&...args) {
    // call your function here, using decltype(args)(args) to perfect forward
  };
}

в С++ 11:

struct min_on_helper {
  template<class...Args>
  auto operator()(Args&&...args)
  -> decltype( min_on_impl(std::declval<Args>()...) )
  {
    return min_on_impl(std::forward<Args>(args)...);
  }
};

является шаблоном. Это позволяет нам передать весь набор перегрузки min_on_impl вокруг как один объект.

template<class F, class T>
struct bind_1st_t {
  F f;
  T t;
  template<class...Args>
  typename std::result_of<F&(T&, Args...)>::type operator()(Args&&...args)&{
    return f( t, std::forward<Args>(args)... );
  }
  template<class...Args>
  typename std::result_of<F const&(T const&, Args...)>::type operator()(Args&&...args)const&{
    return f( t, std::forward<Args>(args)... );
  }
  template<class...Args>
  typename std::result_of<F(T, Args...)>::type operator()(Args&&...args)&&{
    return std::move(f)( std::move(t), std::forward<Args>(args)... );
  }
};
template<class F, class T>
bind_1st_t< typename std::decay<F>::type, typename std::decay<T>::type >
bind_1st( F&& f, T&& t ) {
  return {std::forward<F>(f), std::forward<T>(t)};
}

дает bind_1st.

template<class T>
auto min_on( T&& t )
-> decltype( bind_1st( min_on_helper{}, std::declval<T>() ) )
{
  return bind_1st(min_on_helper{}, std::forward<T>(t));
}

является модульным и решает вашу проблему: обе версии min_on_helper и bind_1st могут быть протестированы независимо.

Вы также можете заменить bind_1st на вызов std::bind, но, по моему опыту, причуды std::bind заставляют меня очень осторожно рекомендовать это кому-либо.