Соответствие функции-члена существования и подписи: параметры

Чтение связанных вопросов "Как вызывать функцию-член, только если объект имеет ее?" и "Можно ли написать шаблон C++ для проверки существования функции?" Я реализую свой собственный класс черт. Цель очень проста, хотя я не могу достичь того, что хотел бы: предоставить класс черт, который статически перенаправляет вызов в соответствующий класс.

Итак, если класс, который я даю своим чертам, имеет, например, void open_file() метод, он вызывает его, в противном случае использовать функцию признаков (NOP один, но теперь выход). Очевидно, это SFINAE-задача, но, будучи не слишком знакомым с процессом, я следовал идеям, как вы увидите.

Все отлично работает для void open_file(), но при попытке void open_file(int) не совпадает и вызывает NOP функция. Это моя попытка (почти дословно из этих двух вопросов!):

template <class Type>
class my_traits
{
    //! Implements a type for "true"
    typedef struct { char value;    } true_class;
    //! Implements a type for "false"
    typedef struct { char value[2]; } false_class;

    //! This handy macro generates actual SFINAE class members for checking event callbacks
    #define MAKE_MEMBER(X) \
        public: \
            template <class T> \
            static true_class has_##X(decltype(&T::X)); \
        \
            template <class T> \
            static false_class has_##X(...); \
        public: \
            static constexpr bool call_##X = sizeof(has_##X<Type>(0)) == sizeof(true_class);

    MAKE_MEMBER(open_file)

public:

    /* SFINAE foo-has-correct-sig :) */
    template<class A, class Buffer>
    static std::true_type test(void (A::*)(int) const)
    {
        return std::true_type();
    }

    /* SFINAE foo-exists :) */
    template <class A>
    static decltype(test(&A::open_file)) test(decltype(&A::open_file), void *)
    {
        /* foo exists. What about sig? */
        typedef decltype(test(&A::open_file)) return_type;
        return return_type();
    }

    /* SFINAE game over :( */
    template<class A>
    static std::false_type test(...)
    {
        return std::false_type();
    }

    /* This will be either `std::true_type` or `std::false_type` */
    typedef decltype(test<Type>(0, 0)) type;

    static const bool value = type::value; /* Which is it? */

    /*  `eval(T const &,std::true_type)`
     delegates to `T::foo()` when `type` == `std::true_type`
     */
    static void eval(Type const & t, std::true_type)
    {
        t.open_file();
    }
    /* `eval(...)` is a no-op for otherwise unmatched arguments */
    static void eval(...)
    {
        // This output for demo purposes. Delete
        std::cout << "open_file() not called" << std::endl;
    }

    /* `eval(T const & t)` delegates to :-
     - `eval(t,type()` when `type` == `std::true_type`
     - `eval(...)` otherwise
     */
    static void eval(Type const &t)
    {
        eval(t, type());
    }

};


class does_match
{
public:
    void open_file(int i) const { std::cout << "MATCHES!" << std::endl; };
};

class doesnt_match
{
public:
    void open_file() const { std::cout << "DOESN'T!" << std::endl; };
};

Как вы видите, я реализовал оба, первый с макросом MAKE_MEMBER просто проверяет существование члена, и это работает. Затем я попытался использовать его для статического SFINAE. if/else То есть, если функция-член существует, вызовите ее, в противном случае используйте предопределенную функцию, но безуспешно (как я уже говорил, я не слишком глубоко в SFINAE).

Вторая попытка - почти дословно из проверки подписи и вопроса о существовании, но я изменил его для обработки параметра. Однако это не сработает:

    does_match   it_does;
    doesnt_match it_doesnt;

    my_traits<decltype(it_does)>::eval(it_does);
    my_traits<decltype(it_doesnt)>::eval(it_doesnt);

    // OUTPUT:
    // open_file() not called
    // open_file() not called

Очевидно, здесь есть проблемы: я не предоставил параметры, но я не знаю, как я могу это сделать.

Я также пытаюсь понять и научиться open_file() это зависит от параметра шаблона, например, с соответствием SFINAE template <class T> open_file(T t)?

Спасибо и ура!

1 ответ

Решение

Проблема в том, что ваш звонок test(&A::open_file):

typedef decltype(test(&A::open_file)) return_type;

всегда соответствует:

static std::false_type test(...)

потому что ваш true-test имеет параметр шаблона недоопределенного типа Buffer:

template<class A, class Buffer>
//                      ~~~~~^
static std::true_type test(void (A::*)(int) const)

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

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

/* SFINAE foo-not-exists */
template <class A>
static std::false_type test(void*, ...);

как запасной вариант для:

static decltype(test(&A::open_file)) test(decltype(&A::open_file), void *)

Совет: вам не нужно указывать тело функции, которая появляется только в неоцененном контексте, как в decltype() оператор.

Наконец, когда вы в конечном итоге сопоставить вызов с void (A::*)(int) const подпись, вы, кажется, забыли об аргументе:

t.open_file(1);
//          ^

Тестовое задание:

my_traits<decltype(it_does)>::eval(it_does);
my_traits<decltype(it_doesnt)>::eval(it_doesnt);

Выход:

MATCHES!
open_file() not called

DEMO


Вся черта может быть значительно упрощена с помощью выражения SFINAE:

template <class Type>
struct my_traits
{
    template <typename T>
    static auto eval(const T& t, int) -> decltype(void(t.open_file(1)))
    {
        t.open_file(1);
    }

    template <typename T>
    static void eval(const T& t, ...)
    {
        std::cout << "open_file() not called" << std::endl;
    }

    static void eval(const Type& t)
    {
        eval<Type>(t, 0);
    }
};

my_traits<decltype(it_does)>::eval(it_does);     // MATCHES!
my_traits<decltype(it_doesnt)>::eval(it_doesnt); // open_file() not called

ДЕМО 2


Бонусный вопрос

Можно ли обобщить подход? Например, если какая-либо функция f использует SFINAE для сопоставления с ней, используя код, который вы опубликовали в DEMO 2, и передает параметры из кода пользователя (например, my_traits::eval(it_does, параметр, параметр))?

template <typename T, typename... Args>
static auto call(T&& t, int, Args&&... args)
    -> decltype(void(std::forward<T>(t).open_file(std::forward<Args>(args)...)))
{
    std::forward<T>(t).open_file(std::forward<Args>(args)...);
}

template <typename T, typename... Args>
static void call(T&& t, void*, Args&&... args)
{
    std::cout << "open_file() not called" << std::endl;
}

template <typename T, typename... Args>
static void eval(T&& t, Args&&... args)
{
    call(std::forward<T>(t), 0, std::forward<Args>(args)...);
}

eval(it_does, 1);    // MATCHES!
eval(it_doesnt, 2);  // open_file() not called
eval(it_does);       // open_file() not called
eval(it_doesnt);     // DOESN'T!

ДЕМО 3

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