Указатель функции с поведением по умолчанию
В моей программе есть много указателей на функции, предоставляемые внешней библиотекой. Некоторые из них возвращают ценность, а некоторые нет.
Если внешняя библиотека не предоставляет функцию (указатель равен нулю) и функция должна возвращать значение, программа должна принять значение по умолчанию (которое известно во время компиляции).
То, что я пытаюсь сделать, - это уменьшить количество операторов if
и обернуть указатель в класс со следующим случаем использования:
enum E { e1, e2, e3 };
UserFunction<uint8_t(int, int), 0> callback1 { user_function_ptr }; // Calls and returns value of user_function_ptr
UserFunction<uint8_t(), 0> callback2 { nullptr }; // Returns 0
UserFunction<uint8_t(), 1> callback3 { nullptr }; // Returns 1
UserFunction<E(int), e1> callback4 { user_function_ptr2 }; // Returns enum value and takes one integer argument
UserFunction<void(int)> callback5 { user_function_ptr3 }; // No return value, one argument
UserFunction<void()> callback6 { nullptr }; // Should perform noop
То, что я получил до сих пор работает для функций, возвращающих значение:
template <class Sign, int Def>
struct UserF;
template <class R, int Def, class... Args>
struct UserF<R(Args...), Def> {
typedef R Signature(Args...);
typedef typename std::add_pointer<Signature>::type SignaturePtr;
static R Default(Args... args) {
return (R) Def;
}
UserF() {
functionToCall = Default;
}
UserF(SignaturePtr userFunction) {
if (userFunction != nullptr) {
functionToCall = userFunction;
} else {
functionToCall = Default;
}
}
R operator() (Args... args) {
return functionToCall(args...);
}
private:
SignaturePtr functionToCall;
};
Проблема с кодом выше заключается в том, что он заставляет значение по умолчанию быть int
. Что я мог бы сделать, так это изменить шаблон UserF
на что-то вроде этого:
template <class R, R Def, class... Args>
struct UserF {
// ...
};
// use case
UserF<E, e1, int> callback; // Returns E, takes int, default e1
Но если это возможно, я бы предпочел использовать
UserF<R(Args...), Default> callback; // preferred
UserF<Default, R(Args...)> callback; // if above is not possible
UserF<void(Args...)> callback; // if no return value
Я бы предпочел не использовать std::function
, поскольку я знаю, что буду иметь дело только с указателями на функции, а не с указателями на функции-члены, объекты-функторы и т. д. Кроме того, boost не допускается (допускается C++11).
Подведем итоги, вопрос в том, как принудительно проверить тип возвращаемого значения по умолчанию.
2 ответа:
template<class Sig> struct return_type; template<class Sig> using return_type_t=typename return_type<Sig>::type; template<class R,class...Args> struct return_type<R(Args...)>{ using type=R; }; template <class Sign, return_type_t<Sign> Def> struct UserF;
- это большая его часть. Для обработки
void
вам понадобится трюк с флагом:struct void_flag{}; template<class T> using flag_void= typename std::conditional<std::is_same<T,void>{},void_flag*,T>::type; template <class Sign, flag_void<return_type_t<Sign>> Def=nullptr> struct UserF; template <class R, R Def, class... Args> struct UserF<R(Args...), Def>{ // body template <class... Args> struct UserF<void(Args...), 0>{ // body
Возникнут проблемы с нецелым не-указателем
R
, так как вы не можете передатьdouble
вtemplate
. Один из подходов состоит в том, чтобы обновитьdouble
доdouble const*
дляDef
, и сделать его авторазыменование:template <class R, R const* Def, class... Args> struct UserF<R(Args...), Def>{ // body
И сделайте что-нибудь вроде
flag_void
, чтобы превратитьT=double
вT=double const*
.
Если вы можете переварить небольшой макрос, вы можете сделать следующее:
template <typename sig, typename T, T v > struct UserF_; template <class R, typename ... Args, typename T, T v > struct UserF_< R(Args...), T, v > { // ... }; #define UserF( F, Default ) UserF_< F, decltype( Default ), Default > UserF(R(Args...), Default) callback;
Вы не можете иметь универсальный шаблон без указания его типа в качестве параметра типа шаблона.