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

Можно ли разрешить использование одного типа std:: function lambdas с разными сигнатурами

У меня есть функция более высокого порядка map, которая похожа на STL for_each и сопоставляет объект std::function над vector вещей.

template<class T, class U>
vector<U> map(function<U (T)> f, vector<T> xs) {
  vector<U> ret;
  for (auto &x: xs)
    ret.push_back(f(x));
  return ret;
}

Теперь я хочу, чтобы эта функция более высокого порядка принимала оба объекта типов function<int (const vector<T>&)> и function<int (vector<T>)>, как показано в прилагаемом минимальном примере.

Проблема заключается в том, что function<int (const vector<T>&)> и function<int (vector<T>)> кажутся конвертируемыми друг к другу (см. head и head2), но map не будет принимать версию ссылок const function<int (const vector<int>&)> (см. Q1).

Можно указать map принять версию ссылки const с явным преобразованием (Q2), но это довольно громоздко.

Мне было интересно, можно ли вообще написать функцию deref, которая удаляет константную ссылку из function<int (const vector<T>&)> и возвращает function<int (vector<T>)>?

(Если выше возможно, тогда мне не придется писать две идентичные перегрузки/реализации карты для const refs).

Спасибо.

#include <vector>
#include <functional>
using namespace std;

template<class T, class U>
vector<U> map(function<U (T)> f, vector<T> xs) {
  vector<U> ret;
  for (auto &x: xs)
    ret.push_back(f(x));
  return ret;
}

int main() {
  vector<vector<int>> m;
  function<int (const vector<int>&)> head  =  [](const vector<int>& a) {return a[0];};
  function<int (const vector<int>&)> head1 =  [](vector<int> a) {return a[0];}; //conversion OK
  function<int (vector<int>)> head2 =  [](const vector<int>& a) {return a[0];}; //conversion OK
  map(head2,m); //OK

  map(head,m); //Q1: problem line, implicit conversion NOT OK
  map(function<int (vector<int>)>(head),m); //Q2: explicit conversion OK
  map(deref(head),m); //Q3: ??How-to, deref takes a std::function f and returns a function with const ref removed from its signature

  return 0;
}

--- EDIT ---

Меня особенно интересует подобная функция deref или мета-функция, которая может удалить const ref из сигнатуры типа объекта std::function, так что я могу как минимум сделать Q2 автоматически.

Я знаю, что, как правильно указывали @Brian и @Manu, использование std::function для указания типов не является обычным, но мне интересно, что я спросил выше, даже выполнимо. Лично я думаю, что код с std::function имеет большую ясность, учитывая, как в С# используются общие типы функций Func<T1, T2, T3, ...,Tn, Tresult>. Это, если стоимость стирания типа допустима.

Я полностью согласен с тем, что С++ может выводить типы возвращаемых данных и давать сообщение об ошибке, если тип неверен. Возможно, это просто вопрос вкуса, и я бы предпочел записать его при написании сигнатур функций.

4b9b3361

Ответ 1

Я понимаю, почему вы используете std::function: вам нужно знать тип возвращаемого преобразования для создания вектора, не так ли?

Но рассмотрим совершенно другой подход. Учитывая metafunction std::result_of, вы можете вычислить тип результата вызова функции, поэтому просто напишите:

template<typename F , typename CONTAINER , typename T = typename std::result_of<F(typename CONTAINER::value_type)>::type>
std::vector<T> map( F f , CONTAINER&& container )
{
    std::vector<T> result;

    for( auto& e : container )
        result.emplace_back( f( e ) );

    return result;
}

Преимущества:

  • Не злоупотреблять std::function: всегда думайте, что делает std::function (т.е. стирает тип), не используйте его как универсальный тип функции.

  • Полагайтесь на утиную печать вместо привязки к типам. Не беспокойтесь, если что-то не так, она не будет компилировать ни один.

  • Работает для любого стандартного библиотечного контейнера, так как мы извлекли тип элемента с помощью признака value_type, вместо прямого использования std::vector.

  • Код намного более ясный и эффективный, так как сокращение использования std::function.

Относительно вопроса "Возможно ли написать функцию, которая принимает лямбды нескольких подписей?"

Используя std::function, вы можете написать что-то похожее на Boost.OverloadedFunction в нескольких строках:

template<typename F , typename... Fs>
struct overloaded_function : public std_function<F> , public std_function<Fs>...
{
    overloaded_function( F&& f , Fs&&... fs ) :
        std_function<F>{ f },
        std_function<Fs>{ fs }...
    {}
};

Где std_function является метафоном, который задает тип функции F возвращает экземпляр std::function с сигнатурой F. Я оставляю это как игру/вызов для читателя.

Вот и все. Улучшите его с помощью make-like функции:

template<typename F , typename... Fs>
overloaded_function<F,Fs...> make_overloaded_function( F&& f , Fs&&... fs )
{
    return { std::forward<F>( f ) , std::forward<Fs>( fs )... };
}

И вы готовы пойти:

auto f = make_overloaded_function( [](){ return 1; } ,
                                   [](int,int){ return 2; } ,
                                   [](const char*){ return 3; } );

f();        //Returns 1
f(1,2);     //Returns 2
f("hello"); //Returns 3

EDIT: "Спасибо. Но то, что я действительно ищу, является мета-функцией, которая берет подпись вызываемого и удаляет const refs из подписи".

Хорошо, позвольте мне попробовать: metafunction std::decay применяет затухание, выполняемое при передаче аргументов по значению данному типу. Это включает в себя удаление cv-классификаторов, удаление ссылок и т.д. Таким образом, metafunction, подобный вашему, может быть чем-то, что принимает тип сигнатуры функции и применяет разложение ко всем его требованиям:

template<typename F>
struct function_decay;

template<typename R typename... ARGS>
struct function_decay<R(ARGS...)>
{
    using type = R(typename std::decay<ARGS>::type...);
};

Это должно делать работу.

Я написал это, потому что вы явно просили его в комментарии, но я настоятельно рекомендую вам использовать альтернативу, которую я показал вам изначально, потому что она имеет много преимуществ по сравнению с вашим путем. Тем не менее, я надеюсь, что этот ответ помог решить вашу проблему.

Ответ 2

Идиоматическое решение состоит в том, чтобы просто позволить map взять произвольный функциональноподобный тип,

template<class T, class F>
auto map(F f, vector<T> xs) -> vector<typename result_of<F(T)>::type> {
  vector<typename result_of<F(T)>::type> ret;
  for (auto &x: xs)
    ret.push_back(f(x));
  return ret;
}

Основная проблема с этим подходом заключается в том, что вы получаете путаные сообщения об ошибках, если F не вызываются с аргументами типа T, или если он возвращает что-то странное, например void.

(Вторая проблема заключается в том, что первый аргумент map не может быть перегруженной функцией, компилятор не сможет просто выбрать перегрузку, которая принимает аргумент типа T.)

(Вы также можете рассмотреть возможность разложения возвращаемого типа F.)