Я пишу шаблонный обработчик событий, который позволяет пользователю отправлять события (в виде структур) подключенным слушателям.
У меня есть функция-член "emit", которая создаст объект Event из предоставленных параметров вариационного шаблона и перенаправит событие. Тем не менее, я хотел бы предоставить специализированную функцию «выдачи», которая будет определять, является ли предоставленный аргумент предварительно сконструированным объектом Event, и в этом случае я мог бы переслать событие, не делая лишней копии.
Моя первая попытка...
template <typename T_Event>
class EventHandler
{
public:
template <typename... T_Args>
void emit(T_Args&&... args)
{
printf("variadic\n");
deliver(T_Event {std::forward<T_Args>(args)...});
}
void emit(const T_Event& event)
{
printf("reference\n");
deliver(event);
}
...
};
Я использовал следующую логику, чтобы опробовать обе функции emit, но оказалось, что функция шаблона с переменным числом аргументов всегда имеет приоритет над ссылочной функцией const.
struct Event { int x; };
EventHandler<Event> handler;
Event event {1};
handler.emit(event);
handler.emit(2);
После еще нескольких исследований мне удалось достичь своей цели, определив две версии функции шаблона с переменным числом аргументов и используя enable_if для выполнения правильной.
template <typename... T_Args>
void emit(T_Args&&... args)
{
printf("variadic\n");
T_Event event {std::forward<T_Args>(args)...};
deliver(event);
}
template <typename... T_Args, typename = std::enable_if<std::is_same<const T_Event&, T_Args...>::value>>
void emit(T_Args&&... args)
{
printf("reference\n");
deliver(std::forward<T_Args>(args)...);
}
Это решение работало именно так, как мне нужно, когда я компилирую с помощью GCC, но если я компилирую с помощью CLANG, я получаю следующее сообщение об ошибке:
call to member function 'emit' is ambiguous
handler.emit(event);
candidate function [with T_Args = <EventB &>]
void emit(T_Args&&... args)
candidate function [with T_Args = <EventB &>, $1 =
std::__1::enable_if<false, void>]
void emit(T_Args&&... args)
Я предполагаю, что я близок к правильному решению, может ли кто-нибудь объяснить, что я делаю неправильно?
Проблема заключается в том, что T_Event&& по-прежнему необходимо создавать до вызова функции emit, а не предоставлять исходные аргументы конструктора Event для функции emit.
invoke
означает std::invoke
?
Нет, это другая функция внутри класса. Я переименую его в примере, чтобы избежать путаницы.
Хитрость заключается в том, чтобы заставить шаблон с переменным числом аргументов отказаться от разрешения перегрузки для пакета параметров, состоящего из одного параметра (необязательная ссылка на необязательную константу с указанием константы).
Это делается путем удаления ссылок и квалификаторов const из каждого типа в пакете параметров, сборки из них кортежа и использования std::is_same
, как и в вашей попытке, для сравнения полученного типа с std::tuple<T_Event>
, а затем неудачного разрешения перегрузки в этом случай, отрицая черты типа (это не std::tuple<T_Event>
.
#include <type_traits>
#include <tuple>
#include <iostream>
template <typename T_Event>
class EventHandler
{
public:
template <typename... T_Args,
typename=std::enable_if_t
<std::negation<std::is_same
<std::tuple<std::remove_const_t
<std::remove_reference_t
<T_Args>>...>,
std::tuple<T_Event>>
>::value>>
void emit(T_Args&&... args)
{
std::cout << "Variadic" << std::endl;
}
void emit(const T_Event& event)
{
std::cout << "Reference" << std::endl;
}
};
int main()
{
EventHandler<const char *> foo;
const char *bar = "foobar";
foo.emit(4);
foo.emit(4, "bar");
foo.emit(bar);
return 0;
}
Протестировано с помощью g++ с параметром -std=c++17. Это должно быть выполнимо с C++11 и C++14 путем повторной реализации некоторых отсутствующих признаков типа. Конечный результат:
$ g++ -o t -std=c++17 t.C
$ ./t
Variadic
Variadic
Reference
Большое спасибо, сэр!
Правильный способ - иметь
const T_Event&
иT_Event&&
.