#c #templates #lambda #c 14 #variadic-templates
#c #шаблоны #лямбда #c 14 #переменные-шаблоны
Вопрос:
У меня есть тип шаблона, который хранит информацию о функции или функции-члене (например, тип возвращаемого значения, число или параметры и так далее).
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(amp;MyType::memFunc)>::isMemberFunction, "");
static_assert(std::is_same<SFuncInfo<decltype(amp;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(amp;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(amp;LambdaType::operator());
static_assert(std::is_same<SFuncInfo<CallOperator>::Ret, float>::value, "");
static_assert(SFuncInfo<CallOperator>::isMemberFunction, "");
Вот ЖИВАЯ ДЕМОНСТРАЦИЯ, с которой кто-то хочет поиграть.
У кого-нибудь есть хорошее решение для этого?
Комментарии:
1. Не
std::decay<decltype(std::declval<T>())>::type
простоstd::decay_t<T>
?2. Ваш текущий код прерывается для функции без аргументов 0, которая возвращает указатель функции-члена с аргументами 0 (и это позволяет избежать этой проблемы только для бесплатных функций, потому что вы можете возвращать только указатели на функции [на которых вы не специализируетесь для бесплатных функций], а не функции). Это потому, что вы перепрофилируете
R
в своих специализациях в качестве входного параметра шаблона, поэтому, еслиSFuncInfo<Ret, Params...>
вы наследуете от пустого,Params
иRet
может быть специализирован снова, вы проиграете. Пожалуйста, не переназначайте параметры шаблона подобным образом. godbolt.org/z/wuGAEc3. @MaxLanghof, спасибо за комментарий. Очень полезно, но не могли бы вы подробнее рассказать о переназначении? Может быть, пример?
4. @Peregrin Первый аргумент шаблона вызывается
R
как вReturnType
. Но семантика ваших специализаций такова: »R
это тип функции (указателя) для проверки». Случается, что это работает в 99% случаев, но тот факт, чтоSFuncInfo<int*>
семантически разрешается в «тип, возвращаемый для функции, которая возвращаетint*
и не принимает параметров», в то время какSFuncInfo<int(*)()>
разрешается в «тип, используемый для вывода информации для функции, которая возвращаетint*
и не принимает параметров», проблематичен, потому чтоR
в последнем случае это не возвращаемое значение, а тип ввода. Я надеюсь, что это имеет смысл.5. @MaxLanghof, я думаю, что понимаю. Но, похоже, эта проблема устранена решением, предложенным Гийомерасико ниже. Вы не согласны?
Ответ №1:
Добавление этой единственной частичной специализации работает на моей стороне:
template<class Lambda>
struct SFuncInfo<Lambda> : SFuncInfo<decltype(amp;Lambda::operator())> { };
Комментарии:
1. это очень элегантное решение, но оказывается, что оно не работает для функций-членов без параметров. Я обновляю ссылку на демонстрационную версию, чтобы добавить этот случай.
Ответ №2:
Решением было бы создать перегрузку, доступную только для вызываемых типов объектов, и наследовать от SFuncInfo
с operator()
типом.
template<typename T>
struct SFuncInfo<T, decltype(void(amp;T::operator()))> : SFuncInfo<decltype(amp;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;
};
Комментарии:
1. Я полностью согласен с тем, что метаданные должны быть отделены от специализаций. В противном случае далеко не тривиально доказать, что это не может быть рекурсивным или неправильно работать каким-либо другим способом (например, если функции возвращают другие указатели на функции).