Reputation: 107
I am writing a templated Event Handler that allows the user to send events (in the form of structs) to connected listeners.
I have an "emit" member function that will construct an Event object from the provided variadic template parameters and forward the event. However, I would like to provide a specialised "emit" function that will detect if the provided argument is a pre-constructed Event object, in which case I could forward the event without making a superfluous copy.
My initial attempt...
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);
}
...
};
I used the following logic to try out both of the emit functions, but it turns out the variadic template function always takes precedence over the const reference function.
struct Event { int x; };
EventHandler<Event> handler;
Event event {1};
handler.emit(event);
handler.emit(2);
After some more research I managed to acheive my goal by defining two versions of the variadic template function and using enable_if to execute the correct one.
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)...);
}
This solution worked exactly as I need when I compile with GCC, but if I compile with CLANG I get the following error message:
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)
I assume that I am close to the correct solution, can anyone explain what I am doing wrong?
Upvotes: 4
Views: 680
Reputation: 118445
The trick is to force the variadic template to fail overload resolution for the parameter pack that consists of a single (optional reference to an optional const-qualified) parameter.
This is done by dropping references and const-qualifiers from each type in the parameter pack, assembling a tuple out of them, and using std::is_same
, just like in your attempt, to compare the resulting type to std::tuple<T_Event>
, and then fail overload resolution in that case by negating the type traits (it is not a 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;
}
Tested with g++ with -std=c++17. This should be doable with C++11 and C++14 by reimplementing some of the missing type traits. End result:
$ g++ -o t -std=c++17 t.C
$ ./t
Variadic
Variadic
Reference
Upvotes: 3