Вывод типа при передаче лямбды в переменную std :: function - PullRequest
0 голосов
/ 17 января 2019

Я пытаюсь получить значения из набора массивов, используя информацию о типе функции, используемой для их обработки. Однако в этом случае вывод типа не выполняется (частично?) Из-за необходимости использовать структуру идентификатора для имени типа std::function. Есть ли способ восстановить вычет здесь?

#include <functional>
#include <iostream>
#include <tuple>

class comp_a
{
public:
    static const size_t id = 0;

    int val = 0;
};

class comp_b
{
public:
    static const size_t id = 1;

    int val = 0;
};

class comp_c
{
public:
    static const size_t id = 2;

    int val = 0;
};

template<size_t size, typename ... Cs>
struct storage
{    
    template<typename T> struct identity { using type = T; };

    template<typename ... Ts>
    void get(size_t index, typename identity<std::function<void(Ts& ...)>>::type f)
    {
        f(std::get<Ts::id>(components)[index] ...);
    }

    std::tuple<std::array<Cs, size> ...> components;
};

int32_t main()
{
    storage<20, comp_a, comp_b, comp_c> storage;

    storage.get(2, [](comp_a& a, comp_c& c) {              // Doesn't work
    // storage.get<comp_a, comp_c>(2, [](comp_a& a, comp_c& c) { // Works
        std::cout << a.val << " " << c.val << std::endl;
    });
}

Я наткнулся на это и это , которые кажутся похожими, но я считаю, что моя ситуация иная, потому что мне нужны переменные типы для доступа к их чертам при получении желаемые значения. Как и в этих примерах, переменные параметры функции обрабатываются как void:

error: cannot convert 'main()::<lambda(comp_a&, comp_c&)>' to 'storage<20, comp_a, comp_b, comp_c>::identity<std::function<void()> >::type' {aka 'std::function<void()>'}

Будет ли руководство по удержанию жизнеспособным в этой ситуации? Информация о типе, похоже, скрыта глубоко в типе std::function, и поэтому я не уверен, как бы вытащить ее из руководства.

Живой пример доступен здесь .

Ответы [ 3 ]

0 голосов
/ 17 января 2019

Вы пометили C ++ 17, так что вы можете использовать std::function руководства по удержаниям

Таким образом, как предлагает Алан Биртлз, вы можете получить лямбду в виде простого типа, преобразовать ее в std::function (инструкции по выводу) и определить тип аргументов.

Что-то как

template<size_t size, typename ... Cs>
struct storage
{    
    template<typename ... Ts>
    void get(size_t index, std::function<void(Ts& ...)> f)
    { f(std::get<Ts::id>(components)[index] ...); }

    template <typename F>
    void get(size_t index, F f)
    { get(index, std::function{f}); }

    std::tuple<std::array<Cs, size> ...> components;
};
0 голосов
/ 17 января 2019

Вы можете использовать трюк с указанием std::function, чтобы вытащить аргументы, но вы действительно не хотите фактически создавать a std::function. Вы хотите сложить в лямбда-стране. std::function добавляет накладные расходы и распределение из-за стирания типа - но вы ничего не делаете, если вам действительно нужны преимущества, которые обеспечивает стирание типа. Это все потеря и нет победы. На самом деле не получается std::function.

Тем не менее, вам, конечно, все еще нужны аргументы. Таким образом, вы можете сделать это:

template <typename T> struct type { };

template <typename F>
void get(size_t index, F f) {
    using function_type = decltype(std::function(f));
    get_impl(index, f, type<function_type>{});
}

По сути, мы берем некоторый вызываемый объект - и затем выводим из него std::function. Это дает нам некоторый тип. В конкретном примере в OP этот тип - std::function<void(comp_a&, comp_b&)>. Затем мы просто перенаправляем этот тип в другую функцию - как пустой объект. Нет накладных расходов. Повторим, мы на самом деле не создаем std::function - мы просто передаем его тип.

Эта другая функция может воспользоваться тем, что знает std::function:

template <typename T> using uncvref_t = std::remove_cv_t<std::remove_reference_t<T>>;

template <typename F, typename R, typename... Args>
void get_impl(size_t index, F f, type_t<std::function<R(Args...)>>) {
    f(std::get<uncvref_t<Args>::id>(components)[index] ...);
}

Вам нужен uncvref_t там для обработки случая, когда Args может быть или не быть ссылкой или cv -качественным.

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

0 голосов
/ 17 января 2019

Я не уверен, для чего нужна структура identity, но ее удаление дает более четкое сообщение об ошибке (сбой вывода шаблона).

Компилятор не может получить тип std::function из лямбды. Чтобы доказать это, компилируется следующее:

storage.get(2, std::function<void(comp_a& a, comp_c& c)>([](comp_a& a, comp_c& c) {              // Doesn't work
    std::cout << a.val << " " << c.val << std::endl;
}));

Итак, чтобы заставить его работать, нам просто нужно помочь компилятору в получении типов. Заимствование у http://www.cplusplus.com/forum/general/223816/ следующих работ:

namespace detail
{
    template < typename T > struct deduce_type;

    template < typename RETURN_TYPE, typename CLASS_TYPE, typename... ARGS >
    struct deduce_type< RETURN_TYPE(CLASS_TYPE::*)(ARGS...) const >
    {
        using type = std::function< RETURN_TYPE(ARGS...) >;
    };
}

template<size_t size, typename ... Cs>
struct storage
{
    template<typename ... Ts>
    void get(size_t index, typename std::function<void(Ts& ...)> f)
    {
        f(std::get<Ts::id>(components)[index] ...);
    }

    template<typename Lambda>
    void get(size_t index, Lambda l)
    {
        get( index, typename detail::deduce_type< decltype( &Lambda::operator() ) >::type( l ) );
    }

    std::tuple<std::array<Cs, size> ...> components;
};
...