简体   繁体   中英

Passing a lambda that captures as argument to a function

This is a follow up to this question .

In that question I had a function package with a function pointer as a parameter. The signature of the functions it accepted looked like this:

template <typename WidgetType, typename Deleter, typename... Dependencies>
using FactoryFunction = std::unique_ptr<WidgetType, Deleter>(*)(std::shared_ptr<Dependencies>...); 

And the signature of package looked like this:

template <typename WidgetType, typename Deleter, typename... Dependencies>
void package(FactoryFunction<WidgetType, Deleter, Dependencies...> factoryFunction)

I was able to pass a lambda to package by using the unary operator:

package(+[]{return std::make_unique<Foo>()});

All well and good, unless I need to capture something in the lambda passed to package . If I change the alias FactoryFunction to:

template <typename WidgetType, typename Deleter, typename... Dependencies>
using FactoryFunction = std::function<std::unique_ptr<WidgetType, Deleter>(std::shared_ptr<Dependencies>...)>;

Then I can pass a capturing lambda to package by doing:

std::function<std::unique_ptr<Foo>()> fooer = [i](){
    return std::make_unique<Foo>();
};

package(fooer);

As mentioned in the original question, this is quite verbose, and it involves a good amount of redundancy, especially when there are more dependency types.

std::function<std::unique_ptr<Bar>(
    std::shared_ptr<Foo>, std::shared_ptr<Bif>, std::shared_ptr<Baz>)> barer= 
        [i](std::shared_ptr<Foo> foo, std::shared_ptr<Bif> bif, std::shared_ptr<Baz> baz) {
            return std::make_unique<Bar>(foo, bif, baz);
        };

package(fooer);

How can I write the signature of package so that I can call package with something as succinct as:

package([capturedVar](std::shared_ptr<Foo> foo, std::shared_ptr<Bif> bif, std::shared_ptr<Baz> baz) {
    foo->proccess(capturedVar);
    return std::make_unique<Bar>(foo, bif, baz);
});

For posterity, here's the code from the original question.

#include <functional>
#include <iostream>
#include <memory>
#include <typeindex>
#include <unordered_map>

// Template alias for widget factory.
template <typename WidgetType, typename Deleter, typename... Dependencies>
using FactoryFunction = std::unique_ptr<WidgetType, Deleter> (*)(std::shared_ptr<Dependencies>...);

// A pair of widgets, one dependant on the other.
struct Foo {
    Foo() { std::cout << "Foo constructed.\n"; }
};
struct Bar {
    Bar(std::shared_ptr<Foo> f) : f_(f) { std::cout << "Bar constructed from Foo ptr.\n"; }
    std::shared_ptr<Foo> f_;
};

// Factory functions to make widgets.
std::unique_ptr<Foo> makeFoo() { return std::make_unique<Foo>(); }
std::unique_ptr<Bar> makeBar(std::shared_ptr<Foo> foo) { return std::make_unique<Bar>(foo); }

// Map of factory functions packaged into function of signature void(), keyed by the type index of the widget type it makes.
std::unordered_map<std::type_index, std::function<void()>> packagedFactories;

// Final resting place of a factory function.
template <typename WidgetType, typename Deleter, typename... Dependencies>
std::shared_ptr<WidgetType> accept(FactoryFunction<WidgetType, Deleter, Dependencies...> factoryFunction) {
    return std::invoke(factoryFunction, std::make_shared<Dependencies>()...);

    // This part has a bit more going on in the real code.
    // Rather than making a new widget for each dependency,
    // dependencies are created elsewhere and fetched.
    // Something like:
 // return std::invoke(factoryFunction, getWidget<Dependencies>()...);
}

// Package a factory for later invocation.
template <typename WidgetType, typename Deleter, typename... Dependencies>
void package(FactoryFunction<WidgetType, Deleter, Dependencies...> factoryFunction) {
    auto tIndex = std::type_index(typeid(WidgetType));

    packagedFactories[tIndex] = [factoryFunction]() { accept(factoryFunction); };
}

void someFunc() {

    package(makeFoo);
    // package(+[]() { return std::unique_ptr<Foo>; }); // This works too.

    int i = 6;

    package(makeBar);
//  package([i](std::shared_ptr<Foo> foo) {  // <- This gives me errors.
//      foo->processThing(i);
//      return std::make_unique<Bar>(foo);
//  }

    packagedFactories[std::type_index(typeid(Foo))]();
    packagedFactories[std::type_index(typeid(Bar))]();
}

int main(int argc, char* argv[]) { someFunc(); }

Instead of trying to let template argument deduction deduce WidgetType and Dependencies... , it may be better to let package accept any type, and design type traits to figure out WidgetType and Dependencies from that.

Possible implementation:

template <typename T>
struct FunctionTraits
{
    using ReturnType = typename FunctionTraits<decltype(&T::operator())>::ReturnType;
    using ArgumentTypes = typename FunctionTraits<decltype(&T::operator())>::ArgumentTypes;
};

template <typename T, typename Ret, typename... Args>
struct FunctionTraits<Ret(T::*)(Args...)>
{
    using ReturnType = Ret;
    using ArgumentTypes = std::tuple<Args...>;
};

template <typename T, typename Ret, typename... Args>
struct FunctionTraits<Ret(T::*)(Args...) const>
{
    using ReturnType = Ret;
    using ArgumentTypes = std::tuple<Args...>;
};

template <typename Ret, typename... Args>
struct FunctionTraits<Ret(*)(Args...)>
{
    using ReturnType = Ret;
    using ArgumentTypes = std::tuple<Args...>;
};

template <typename T>
struct ArgsTuple;

template <typename... Args>
struct ArgsTuple<std::tuple<std::shared_ptr<Args>...>>
{
    static auto make()
    {
        return std::make_tuple(std::make_shared<Args>()...);
    }
};

std::unordered_map<std::type_index, std::function<void()>> packagedFactories;

template <typename FactoryFunction>
auto accept(FactoryFunction factoryFunction) {
    auto args = ArgsTuple<typename FunctionTraits<FactoryFunction>::ArgumentTypes>::make();
    return std::apply(factoryFunction, args);
}

template <typename FactoryFunction>
void package(FactoryFunction factoryFunction) {
    using WidgetType = typename FunctionTraits<FactoryFunction>::ReturnType::element_type;
    auto tIndex = std::type_index(typeid(WidgetType));

    packagedFactories[tIndex] = [factoryFunction]() { accept(factoryFunction); };
}

Live Demo

In this example, FunctionTraits uses partial specialization to figure out the return type and argument types of a function-like thing (assuming it doesn't have multiple overloads of operator() ). ArgsTuple then uses partial-specialization to build a tuple of shared_ptr s to pass to that function-like thing.

This allows package and accept to accept any type and figure out WidgetType and Dependencies using FunctionTraits and ArgsTuple .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM