Инициализирайте клас, съдържащ std::функция с ламбда

Създадох шаблонен клас, съдържащ std::function като член по следния начин:

template<typename Ret, typename... Args>
class Foo
{
private:
    std::function<Ret(Args...)> _func;

public:
    Foo(const std::function<Ret(Args...)>& func):
        _func(func)
    {}
};

За да не се налага да посочвам аргументите и връщания тип на предадената функция, създадох някои make_foo претоварвания:

template<typename Ret, typename... Args>
auto make_foo(Ret (&func)(Args...))
    -> Foo<Ret, Args...>
{
    return { std::function<Ret(Args...)>(func) };
}

template<typename Ret, typename... Args>
auto make_foo(const std::function<Ret(Args...)>& func)
    -> Foo<Ret, Args...>
{
    return { func };
}

Въпреки това не успях да създам make_foo претоварване, което приема ламбда като параметър:

template<typename Ret, typename... Args>
auto make_foo(??? func)
    -> Foo<Ret, Args...>
{
    return { std::function<Ret(Args...)>(func) };
}

Просто не мога да намеря начин типът връщане и типовете аргументи да се извеждат автоматично от ламбда. Има ли идиоматичен начин за решаване на такъв проблем?


person Morwenn    schedule 12.11.2013    source източник
comment
Просто трябва да можете да замените всичко с T (или F, ако предпочитате).   -  person chris    schedule 12.11.2013
comment
Може би разгледайте тази библиотека Boost.   -  person Kerrek SB    schedule 12.11.2013
comment
Претоварването, което приема std::function като аргумент, е достатъчно, за да разреши ламбда. Всъщност трябва да го използвате като единственото претоварване, защото приема както ламбда, така и функционални указатели.   -  person 0x499602D2    schedule 12.11.2013
comment
@0x499602D2 Е, все пак изглежда не работи :/   -  person Morwenn    schedule 12.11.2013
comment
@0x499602D2: Не е за ламбда. Има твърде много дефинирани от потребителя реализации.   -  person Kerrek SB    schedule 12.11.2013
comment
@chris Опитах, но съхранявам кортеж от тип std::tuple<Args...> в тялото на класа. Така че трябва да имам Args... като параметър на шаблон на клас.   -  person Morwenn    schedule 12.11.2013
comment
ако също съхранявате кортеж с аргументи, защо просто не получите техните типове от споменатите аргументи?   -  person R. Martinho Fernandes    schedule 13.11.2013
comment
@R.MartinhoFernandes Вероятно защото std::tuple<Args...> е частен и make_foo няма достъп до него.   -  person Morwenn    schedule 13.11.2013
comment
това не обяснява нищо. какво съхранява кортежът? откъде идват тези аргументи? вземете го от там. (моля, не казвайте двуфазна инициализация)   -  person R. Martinho Fernandes    schedule 13.11.2013
comment
@R.MartinhoFernandes Ако искате историята whome, пълният клас Foo всъщност беше функтор, мемоизиращ дадена чиста функция. Затова използвам std::unordered_map<std::tuple<Args...>, Ret> за кеширане на двойките аргументи/резултат.   -  person Morwenn    schedule 13.11.2013


Отговори (2)


Добре, значи си мислех, че ще умра, но най-накрая успях да го направя ç_ç

Първо, използвах обичайните индекси. Тъй като нямам официалните, използвах стари индекси, които написах преди няколко месеца:

template<std::size_t...>
struct indices {};

template<std::size_t N, std::size_t... Ind>
struct make_indices:
    make_indices<N-1, N-1, Ind...>
{};

template<std::size_t... Ind>
struct make_indices<0, Ind...>:
    indices<Ind...>
{};

След това използвах някои функционални характеристики, намерени някъде в StackOverflow. Те са хубави и мисля, че са еквивалентни на библиотеката Boost, свързана в коментарите:

template<typename T>
struct function_traits:
    function_traits<decltype(&T::operator())>
{};

template<typename C, typename Ret, typename... Args>
struct function_traits<Ret(C::*)(Args...) const>
{
    enum { arity = sizeof...(Args) };

    using result_type = Ret;

    template<std::size_t N>
    using arg = typename std::tuple_element<N, std::tuple<Args...>>::type;
};

След това успях да напиша правилна функция make_foo и функция за нейното изпълнение, тъй като и двете трябва да използват индекси. Внимавайте, това е просто грозно:

template<typename Function, std::size_t... Ind>
auto make_foo_(Function&& func, indices<Ind...>)
    -> Foo<
        typename function_traits<typename std::remove_reference<Function>::type>::result_type,
        typename function_traits<typename std::remove_reference<Function>::type>::template arg<Ind>...>
{
    using Ret = typename function_traits<typename std::remove_reference<Function>::type>::result_type;
    return { std::function<Ret(typename function_traits<typename std::remove_reference<Function>::type>::template arg<Ind>...)>(func) };
}

template<typename Function, typename Indices=make_indices<function_traits<typename std::remove_reference<Function>::type>::arity>>
auto make_foo(Function&& func)
    -> decltype(make_foo_(std::forward<Function>(func), Indices()))
{
    return make_foo_(std::forward<Function>(func), Indices());
}

Кодът е някак грозен и нечетлив, но определено работи. Надяваме се, че сега не разчита на някакво поведение, дефинирано от внедряването. Освен това благодаря на всички за съветите, помогнаха! :)

int main()
{
    auto lambda = [](int i, float b, long c)
    {
        return long(i*10+b+c);
    };

    auto foo = make_foo(lambda);
    std::cout << foo(5, 5.0, 2) << std::endl; // 57, it works!
}

А ето и пример на живо :)

person Morwenn    schedule 12.11.2013
comment
Да бях открил това предложение по-рано, щях да съм по-малко болезнен. .. - person Morwenn; 13.11.2013
comment
Morwenn идваше на помощ 2 пъти за седмица... случайно имах нужда от това. Изглежда, че работи, но видях някои FUD над генерични ламбда на подобни отговори, които всъщност не отговарят на моя случай на употреба, както този. Тъй като последиците от написаното тук ми е трудно да проследя, само ще го спомена и ще попитам смятате ли, че генеричните ламбда ще нарушат това? (Забележка: освен това връзката към предложението вече е мъртва. ..) - person HostileFork says dont trust SE; 19.12.2014
comment
@HostileFork Вероятно ще се счупи. Въпреки че правилно обработва функции и ламбда с фиксирани типове, той се поврежда веднага щом има претоварвания или шаблони, тъй като &T::operator() не може да разреши адреса на претоварена функция. Изводът е, че ако можете да абстрахирате типа на функцията, моля, направете го и след това, ако нищо друго не работи, можете да опитате да използвате function_traits (имайте предвид, че те също съществуват в Boost). Мъртвата връзка беше връзка към проблем, който също предоставяше някои функции. - person Morwenn; 19.12.2014

Имам пример, който работи с mutable ламбда. Не мога да разбера как да направя правилната квалификация на член на CV.

Първо, ето шаблона на функцията, който търсим:

#include <functional>

template <typename R, typename ...Args>
void foo(std::function<R(Args...)> f)
{ }

Сега ще оставим шаблон на функция bar да вземе произволна ламбда и да извика правилната версия на foo, като провери типа на operator() на ламбда:

#include <type_traits>

template <typename> struct remove_member;

template <typename C, typename T>
struct remove_member<T C::*>
{ using type = T; };

template <typename F>
void bar(F f)
{
    using ft = decltype(&F::operator());
    foo(std::function<typename remove_member<ft>::type>(f));
}

Пример:

int q;
bar([&](int a, int b) mutable -> int { q = a + b; return q / b; });

Можете да използвате нормални const ламбда с тази модифицирана характеристика, въпреки че не ми харесва да изписвам типа на функцията:

template <typename C, typename R, typename ...Args>
struct remove_member<R (C::*)(Args...) const>
{ using type = R(Args...); };

Мислех, че може да работи с оригиналния код, ако използвам typename std::remove_cv<T>::type, но поне на GCC това не работи поради някакъв странен __attribute__((const)), който е зададен на типа оператор на ламбда, който изглежда пречи на специализацията на шаблона.

person Kerrek SB    schedule 12.11.2013
comment
Гласуване в полза на вашето предизвикателство към гласуващия против и отговор, въпреки че използвах решението на @Morwenn (макар и малко объркано). Ще ми е любопитно какво мислите за него и ако генерични ламбда добавете някакви криви топки, защото в краткото ми проучване те бяха споменати като побеждаващи някои хакове при решаването на този проблем (въпреки че е трудно да се намери въпрос, който точно съответства, без някой да каже, че това не е това, което наистина искате) . - person HostileFork says dont trust SE; 19.12.2014
comment
@HostileFork: Предполагам, че не бихте могли да използвате decltype(&F::operator()) на обща ламбда, тъй като тази членска функция е шаблон. - person Kerrek SB; 19.12.2014