У меня есть тип шаблона, в котором хранится информация о функции или функции-члене (например, тип возвращаемого значения, число или параметры и т. д.).
template<class R, class... FuncParams>
struct SFuncInfo
{
using Signature = R(FuncParams...);
using Ret = R;
static constexpr size_t numParams = sizeof...(FuncParams);
};
// member
template<class T, class Ret, class... Params>
struct SFuncInfo<Ret(T::*)(Params...)> : SFuncInfo<Ret, Params...>
{
static constexpr bool isMemberFunction = true;
};
// function
template<class R, class... FuncParams>
struct SFuncInfo<R(FuncParams...)> : SFuncInfo<R, FuncParams...>
{
static constexpr bool isMemberFunction = false;
};
Вот как это можно использовать:
int func(const char* str) { return 1; }
struct MyType
{
bool memFunc(int val, float fl) { return true; }
};
int main()
{
static_assert(!SFuncInfo<decltype(func)>::isMemberFunction, "");
static_assert(std::is_same<SFuncInfo<decltype(func)>::Ret, int>::value, "");
static_assert(SFuncInfo<decltype(&MyType::memFunc)>::isMemberFunction, "");
static_assert(std::is_same<SFuncInfo<decltype(&MyType::memFunc)>::Ret, bool>::value, "");
}
Этот код компилируется. Но я также хочу обрабатывать случаи с лямбда-выражениями. Что-то вроде этого:
auto lambda = [](int, bool) -> float { return 3.14f; };
static_assert(SFuncInfo<decltype(lambda)>::isMemberFunction, "");
static_assert(std::is_same<SFuncInfo<decltype(lambda)>::Ret, float>::value, "");
Я пробовал другой вариант. Некоторые из них перечислены ниже.
template<class T>
struct SFuncInfo<T, decltype(T())>
{
static constexpr bool isMemberFunction = true;
};
template<class T>
struct SFuncInfo<T, decltype(&std::decay<decltype(std::declval<T>())>::type::operator())>
{
static constexpr bool isMemberFunction = true;
};
Это не относится ни к одной из этих специализаций.
Кстати, код ниже тоже компилируется:
auto lambda = [](int, bool) -> float { return 3.14f; };
using LambdaType = std::decay<decltype(std::declval<decltype(lambda)>())>::type;
using CallOperator = decltype(&LambdaType::operator());
static_assert(std::is_same<SFuncInfo<CallOperator>::Ret, float>::value, "");
static_assert(SFuncInfo<CallOperator>::isMemberFunction, "");
Вот ЖИВАЯ ДЕМО это кто-то хочет побаловаться.
У кого-нибудь есть хорошее решение для этого?
Ваш текущий код ломается для функции без аргументов с 0, которая возвращает указатель функции-члена с 0 аргументами (и это только узко позволяет избежать этой проблемы для бесплатных функций, потому что вы можете возвращать только указатели функций [на которых вы не специализируетесь для свободных функций], а не функции). Это связано с тем, что вы переназначаете R в своих специализациях в качестве входного параметра шаблона, поэтому, если SFuncInfo<Ret, Params...>, от которого вы наследуете, имеет пустое Params и Ret, на котором можно снова специализироваться, вы проигрываете. Пожалуйста, не изменяйте параметры шаблона таким образом. godbolt.org/z/wuGAEc
@MaxLanghof, спасибо за комментарий. Очень полезно, но не могли бы вы подробнее рассказать о перепрофилировании? Может пример?
@Peregrin Первый аргумент шаблона называется R, как в ReturnType. Но семантика вашей специализации такова: «R — это тип функции (указателя) для проверки». Это работает в 99% случаев, но тот факт, что SFuncInfo<int*> семантически разрешается в «тип вернулся для функции, которая возвращает int* и не принимает параметров», в то время как SFuncInfo<int(*)()> разрешается в «тип, используемый для делать вывод информации для функции, которая возвращает int* и не принимает никаких параметров» проблематично, потому что R в последнем случае является не возвращаемым значением, а типом ввода. Я надеюсь это имеет смысл.
@MaxLanghof, кажется, я понимаю. Но похоже, что эта проблема устранена решением, предложенным GuillaumeRacicot ниже. Вы бы не согласились?
@Peregrin Да, это решение делится на SFuncInfoBase, которое хранит информацию, и SFuncInfo, которое отвечает за определение того, что хранить в своем SFuncInfoBase.





Добавление этой единственной частичной специализации работает на моей стороне:
template<class Lambda>
struct SFuncInfo<Lambda> : SFuncInfo<decltype(&Lambda::operator())> { };
это очень элегантное решение, но оказывается, что оно не работает для функций-членов без параметров. Я обновляю ссылку на живую демонстрацию, чтобы добавить этот случай.
Решением было бы сделать перегрузку, доступную только для вызываемых типов объектов, и наследовать от SFuncInfo с типом operator().
template<typename T>
struct SFuncInfo<T, decltype(void(&T::operator()))> : SFuncInfo<decltype(&T::operator())> {};
// constraint ----------------^
Однако для поддержки этого я разделил специализации и классы метаданных, разделив их на SFuncInfo и SFuncInfoBase:
template<class R, class... FuncParams>
struct SFuncInfoBase
{
using Signature = R(FuncParams...);
using Ret = R;
static constexpr size_t numParams = sizeof...(FuncParams);
};
template<class T, typename = void>
struct SFuncInfo;
// member
template<class T, class Ret, class... Params>
struct SFuncInfo<Ret(T::*)(Params...)const> : SFuncInfo<Ret(T::*)(Params...)> {};
template<class T, class Ret, class... Params>
struct SFuncInfo<Ret(T::*)(Params...)> : SFuncInfoBase<Ret, Params...>
{
static constexpr bool isMemberFunction = true;
};
Я полностью согласен с тем, что метаданные должны быть отделены от специализаций. В противном случае далеко не тривиально доказать, что это не может быть рекурсивным или плохо себя вести каким-либо другим образом (например, если функции возвращают другие указатели функций).
Разве
std::decay<decltype(std::declval<T>())>::typeне простоstd::decay_t<T>?