简体   繁体   中英

Constructor template argument deduction with std::function as parameter

I have a class with a template constructor like the following:

class Foo {
private:

    std::unordered_map<std::type_index, std::vector<std::function<void(BaseT*)>>> funcs;

public:

    template<class T> Foo(const std::function<void(T* arg)>& func) {
        auto funcToStore = [func](BaseT* a) { func(static_cast<T*>(a)); };
        this->funcs[std::type_index(typeid(T))].push_back(funcToStore);
    }

}

This classes' constructor takes a function parameter with an argument of a type T derived from some base type BaseT and stores this function in a map of vectors utilizing std::type_info of T for the key.

As this is a template constructor and not an ordinary function, explicitly specifying the template parameter won't work, because this is no allowed syntax:

Foo* foo = new Foo<MyT>([](MyT* arg) { ... });

Omitting the explicit <MyT> also won't work because the template parameter can't be deduced from the lambda argument type.

So one solution would be to wrap the lambda in a std::function object:

Foo* foo = new Foo(std::function<void(MyT*)>([](MyT* arg) { ... }));

But this is obviously not a nice readable syntax.

The best I've come up so far is making use of an alias for the std::function :

template<class T> using Func = std::function<void(T*)>;

Foo* foo = new Foo(Func<MyT>([](MyT* arg) { ... }));

This is shorter, and when using the auto keyword in the lambda argument I would have to specify the actual type MyT only once, so this seems to be a nice solution in the end.

But can there be any other, even shorter solution? So that it wouldn't be necessary to wrap the lambda? Like:

Foo* foo = new Foo([](MyT* arg) { ... });

Use a normal template parameter instead of std::function :

class Foo {
    std::unordered_map<size_t, std::vector<BaseT*>> funcs;

public:
    template<class T> Foo(const T& func) {
        // ...
    }

};

Now deduction will happen correctly, and your code won't suffer from the overhead of std::function .

What if you want to obtain the type of the first parameter of the lambda?

You'd have to make something like this:

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

template<typename R, typename C, typename... Args>
struct function_traits<R(C::*)(Args...) const> {
    using arguments = std::tuple<Args...>;
    using result = R;
};

Of course, if you want to support every possible cases of function types, you need the 32 specialisations

Now, you can extract the arguments types and even the return type if needed:

template<class T> Foo(const T& func) {
    using Arg = std::tuple_element_t<0, typename  function_traits<T>::arguments>;
    auto funcToStore = [func](BaseT* a) { func(static_cast<Arg>(a)); };

    funcs[typeid(Arg).hash_code()].push_back(funcToStore);
}

Also, since you recieve a const T& in your constructor, you might want to constraint your function to be only callable with what could compile:

template<typename T>
using is_valid_foo_function = std::is_convertible<
    BaseT*, // form
    std::tuple_element_t<0, typename function_traits<T>::arguments> // to
>;

And use the constraint like that:

template<class T, std::enable_if_t<is_valid_foo_function<T>::value>* = nullptr>
Foo(const T& func) {
    // ...
}

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