У меня есть класс с конструктором шаблона, как показано ниже:
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) { ... });
Используйте обычный параметр шаблона вместо 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) {
// ...
}
Других решений пока нет …