Соответствие функции-члена существования и подписи: параметры
Чтение связанных вопросов "Как вызывать функцию-член, только если объект имеет ее?" и "Можно ли написать шаблон 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
Вся черта может быть значительно упрощена с помощью выражения 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
Бонусный вопрос
Можно ли обобщить подход? Например, если какая-либо функция 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!