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

Чтение связанных вопросов "Как вызвать члена работать, только если он есть у объекта?" и "Можно ли написать шаблон 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, то есть, если функция-член существует, то вызовите ее, в противном случае используйте предопределенную функцию, но безуспешно (как я уже сказал, я не слишком глубоко в СФИНАЭ).

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

    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)?

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


person senseiwa    schedule 24.12.2014    source источник


Ответы (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

ДЕМО


Весь трейт можно значительно упростить, используя выражение 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

person Piotr Skotnicki    schedule 24.12.2014
comment
Второй вариант очень интересный! Как вы думаете, можно ли обобщить подход? Например, для любой функции f используйте SFINAE для ее сопоставления, используя код, который вы разместили в DEMO 2, и передавая параметры из пользовательского кода (например, my_traits<decltype(it_does)>::eval(it_does, parameter, parameter))? - person senseiwa; 24.12.2014
comment
Спасибо @piotr-s, это круто! Если я не ошибаюсь, взглянув на сборку релиза, накладных расходов во время выполнения нет, так как я вижу только invoke void @_ZNK3seq10does_match9open_fileEi(%"class.seq::does_match"* %it_does, i32 4321), однако я вижу некоторые вызовы call void @llvm.dbg.value(metadata !{%"class.seq::does_match"* %it_does}, i64 0, metadata !13069), !dbg !13059, но я думаю, что это не повлияет на мой код. Я интерпретирую это в правильном направлении? Большое спасибо! - person senseiwa; 26.12.2014