Вывод аргумента шаблона конструктора с std :: function в качестве параметра

У меня есть класс с конструктором шаблона, как показано ниже:

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

}

Этот конструктор классов принимает параметр функции с аргументом типа T происходит от некоторого базового типа BaseT и сохраняет эту функцию в карте векторов, использующих std::type_info из T за ключ.

Как это шаблон конструктор и не обычная функция, явно указывающая параметр шаблона, не будет работать, потому что это недопустимый синтаксис:

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

Опуская явное <MyT> также не будет работать, потому что параметр шаблона не может быть выведен из типа аргумента лямбда.

Таким образом, одним из решений было бы обернуть лямбда в std::function объект:

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

Но это явно не хороший читаемый синтаксис.

Лучшее, что я придумала, это использование псевдоним для std::function:

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

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

Это короче, и при использовании auto Ключевое слово в аргументе лямбда, я должен был бы указать фактический тип MyT только один раз, так что это, кажется, хорошее решение в конце.

Но может ли быть какое-то другое, даже более короткое решение? Чтобы не было необходимости оборачивать лямбду? Подобно:

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

1

Решение

Используйте обычный параметр шаблона вместо std::function:

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

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

};

Теперь вычет будет происходить правильно, и ваш код не пострадает от std::function,

Что если вы хотите получить тип первого параметра лямбды?

Вы должны сделать что-то вроде этого:

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;
};

Конечно, если вы хотите поддерживать все возможные случаи типов функций, вам нужно 32 специализации

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

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

Кроме того, так как вы получаете const T& в вашем конструкторе вы можете захотеть ограничить вашу функцию только для вызова с тем, что может скомпилировать:

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

И используйте ограничение следующим образом:

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

Другие решения

Других решений пока нет …

По вопросам рекламы ammmcru@yandex.ru
Adblock
detector