'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(); }


Solution 1:[1]

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_ptrs 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.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Miles Budnek