Проверьте, существует ли шаблонная функция-член SFINAE - PullRequest
0 голосов
/ 19 марта 2020

Следующая проблема: я хочу проверить, существует ли шаблонный метод, поэтому я адаптировал приведенные здесь примеры: Можно ли написать шаблон для проверки существования функции?

#include <cstdio>
#include <type_traits>

#define CHECK4_MEMBER_FUNC(RETTYPE,FUNCTION,...) \
template <class ClassType> \
class CIfCheck_##FUNCTION \
{\
private: \
    template <class MemberPointerType> \
    static std::true_type testSignature(RETTYPE (MemberPointerType::*)(__VA_ARGS__)); \
\
    template <class MemberPointerType> \
    static std::false_type testExistence(...); \
   \
    template <class MemberPointerType> \
    static decltype(testSignature(&MemberPointerType::FUNCTION)) testExistence(std::nullptr_t); \
public: \
    using type = decltype(testExistence<ClassType>(nullptr));\
    static const bool value = type::value; \
};


    class Bla
    {
    public:
        template <typename SomeType>
        bool init(SomeType someValue)
        {
            ///
            return true;
        }

        void exec()
        {
            return;
        }
    };

    CHECK4_MEMBER_FUNC(bool, init, int);
    CHECK4_MEMBER_FUNC(void, exec, void);

int main()
{
  Bla blaObj;
  blaObj.init<int>(2);
  static_assert(CIfCheck_exec<Bla>::value, "no exec");
  static_assert(CIfCheck_init<Bla>::value, "no init");

  return 0;
}

но, к сожалению, static_assert() срабатывает для init() (поскольку специализация, вероятно, будет оценена позднее, когда объект будет создан в main()).

Я пытался с явной специализацией на члене, но она все еще терпит неудачу:

template<>
bool Bla::init<int>(int item)
{
    int temp = item*2; // do with item something
    return false;
}

PS: побочный вопрос (возможно, другой вопрос topi c будет иметь больше смысла:

std::false_type testExistence(...);

Почему именно я должен передать аргумент здесь? Если я уберу опцию variadi c аргумент ...nullptr и nullptr_t), ошибки компилятора из-за неоднозначного существования testExistence().

1 Ответ

1 голос
/ 19 марта 2020

, но, к сожалению, static_assert запускается для init (поскольку специализация, вероятно, будет оценена позднее, так как объект создается в main ())

Не совсем.

Проблема в том, что init() - это шаблонный метод, поэтому при написании

decltype(testSignature(&MemberPointerType::FUNCTION))

указатель не выбирается, поскольку компилятор не может выбрать правильный метод.

Вы можете попробовать с

decltype(testSignature(&MemberPointerType::template FUNCTION<__VA_ARGS__>))

, но теперь не работает для exec(), который не является методом шаблона

Для работы как с шаблоном, так и без шаблона ... не простое прохождение через макрос variadi c, потому что часть variadi c не может быть пустой ... но я предлагаю что-то следующее

template <typename...>
struct wrap
 { };

#define CHECK4_MEMBER_FUNC(RETTYPE,FUN,...) \
template <class ClassType> \
class CIfCheck_##FUN \
{\
private: \
    template <typename MPT> \
    static auto testSig (wrap<void>) \
       -> std::is_same<decltype(std::declval<MPT>().FUN()),\
                       RETTYPE>; \
    \
    template <typename MPT, typename ... As> \
    static auto testSig (wrap<As...>) \
       -> std::is_same<decltype(std::declval<MPT>().FUN(std::declval<As>()...)), \
                       RETTYPE>; \
    \
    template <typename...> \
    static std::false_type testSig (...);\
    \
public: \
    using type = decltype(testSig<ClassType>(wrap<__VA_ARGS__>{}));\
    static const bool value = type::value; \
};

Заметьте, что я добавил структуру wrap обернуть типы параметров шаблона; обычно используется std::tuple, но в этом случае нам нужно wrap<void>, потому что std::tuple<void> выдает ошибку.

Обратите внимание, что мое решение отличается от другой точки зрения (и может быть лучше или хуже, согласно вашим конкретным c потребностям): ваше решение проверяет наличие метода с точной подписью; В моем решении проверьте, есть ли метод, который можно вызвать с заданным списком аргументов.

Конкретный пример: предположим, что существует метод Bla::foo(), который принимает значение long

    void foo (long)
     { }

В вашем решении, если вы проверите параметр int

CHECK4_MEMBER_FUNC(void, foo, int);

static_assert( false == CIfCheck_foo<Bla>::value, "no foo with int");

, вы получите значение false из CIfCheck_foo, поскольку в Bla нет метода foo введите void(&BLA::*)(int) (есть void(&BLA::*)(long), который отличается).

С моим методом вы получите значение true от CIfCheck_foo, потому что foo(long) принимает также int значение (и возвращаемый тип void).


std :: false_type testExistence (...);

Почему именно я должен передать аргумент здесь? Если я удалю опцию variadi c аргумент ...nullptr и nullptr_t), ошибки компилятора из-за неоднозначного существования testExistence().

То testExistence(), как

    template <typename...> \
    static std::false_type testSig (...);\

- второй выбор.

Я имею в виду ... когда вы вызываете макрос testExistence() внутри decltype()

decltype(testExistence<ClassType>(nullptr));

или мой вызов макроса testSig() внутри decltype()

decltype(testSig<ClassType>(wrap<__VA_ARGS__>{}));

вызов, функционирующий с аргументом (nullptr или wrap<__VA_ARGS__>{}).

Когда доступен первый выбор (когда присутствует RETTYPE (MemberPointerType::*)(__VA_ARGS__) в вашем случае, когда в моем примере вызывается метод с необходимыми аргументами), компилятор выбирает эту версию и возвращает std::true_type (или std::is_same в моем коде).

Но когда первый выбор недоступен?

Требуются вторые варианты, версии, возвращающие std::false. Но звонок с аргументом. Многоточие здесь - это старый список аргументов C в стиле variadi c и допускается ноль или более аргументов, поэтому принимайте также один аргумент.

Если вы удалите многоточие (...), второй выбор больше не может принимать аргумент (становится функцией с нулевым аргументом), и вы получаете ошибку компиляции, потому что компилятор не находит функцию второго выбора, совместимую с аргументом.

...