Частично специализированный шаблон класса от lambda

У меня есть тип шаблона, в котором хранится информация о функции или функции-члене (например, тип возвращаемого значения, число или параметры и т. д.).

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, "");

Вот ЖИВАЯ ДЕМО это кто-то хочет побаловаться.

У кого-нибудь есть хорошее решение для этого?

Разве std::decay<decltype(std::declval<T>())>::type не просто std::decay_t<T>?

Quentin 05.04.2019 16:50

Ваш текущий код ломается для функции без аргументов с 0, которая возвращает указатель функции-члена с 0 аргументами (и это только узко позволяет избежать этой проблемы для бесплатных функций, потому что вы можете возвращать только указатели функций [на которых вы не специализируетесь для свободных функций], а не функции). Это связано с тем, что вы переназначаете R в своих специализациях в качестве входного параметра шаблона, поэтому, если SFuncInfo<Ret, Params...>, от которого вы наследуете, имеет пустое Params и Ret, на котором можно снова специализироваться, вы проигрываете. Пожалуйста, не изменяйте параметры шаблона таким образом. godbolt.org/z/wuGAEc

Max Langhof 05.04.2019 17:26

@MaxLanghof, спасибо за комментарий. Очень полезно, но не могли бы вы подробнее рассказать о перепрофилировании? Может пример?

Peregrin 08.04.2019 08:56

@Peregrin Первый аргумент шаблона называется R, как в ReturnType. Но семантика вашей специализации такова: «R — это тип функции (указателя) для проверки». Это работает в 99% случаев, но тот факт, что SFuncInfo<int*> семантически разрешается в «тип вернулся для функции, которая возвращает int* и не принимает параметров», в то время как SFuncInfo<int(*)()> разрешается в «тип, используемый для делать вывод информации для функции, которая возвращает int* и не принимает никаких параметров» проблематично, потому что R в последнем случае является не возвращаемым значением, а типом ввода. Я надеюсь это имеет смысл.

Max Langhof 08.04.2019 09:47

@MaxLanghof, кажется, я понимаю. Но похоже, что эта проблема устранена решением, предложенным GuillaumeRacicot ниже. Вы бы не согласились?

Peregrin 08.04.2019 12:52

@Peregrin Да, это решение делится на SFuncInfoBase, которое хранит информацию, и SFuncInfo, которое отвечает за определение того, что хранить в своем SFuncInfoBase.

Max Langhof 08.04.2019 15:01
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
4
6
268
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Добавление этой единственной частичной специализации работает на моей стороне:

template<class Lambda>
struct SFuncInfo<Lambda> : SFuncInfo<decltype(&Lambda::operator())> { };

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

Peregrin 05.04.2019 17:32
Ответ принят как подходящий

Решением было бы сделать перегрузку, доступную только для вызываемых типов объектов, и наследовать от 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;
};

Живой пример

Я полностью согласен с тем, что метаданные должны быть отделены от специализаций. В противном случае далеко не тривиально доказать, что это не может быть рекурсивным или плохо себя вести каким-либо другим образом (например, если функции возвращают другие указатели функций).

Max Langhof 05.04.2019 17:05

Другие вопросы по теме