Частично специализировать шаблон класса с помощью lambda

#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/wuGAEc

3. @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. Я полностью согласен с тем, что метаданные должны быть отделены от специализаций. В противном случае далеко не тривиально доказать, что это не может быть рекурсивным или неправильно работать каким-либо другим способом (например, если функции возвращают другие указатели на функции).