Reputation: 436
I'm trying to limit template deduction only to objects from the same hierarchy.
The code below compiles
template<class T>
void RegisterAction(const string& actionId, bool(T::*f)())
{
m_actions.emplace(actionId, std::bind(f, static_cast<T*>(this)));
}
but this code doesn't
template<class T, typename std::enable_if_t<std::is_base_of<BaseClass, T>::value>>
void RegisterAction(const string& actionId, bool(T::*f)())
{
m_actions.emplace(actionId, std::bind(f, static_cast<T*>(this)));
}
m_actions is of type std::unordered_map<string, std::function<bool()>>
Here is the error from Visual Studio
'BaseClass::RegisterAction': no matching overloaded function found
error C2783: 'void BaseClass::RegisterAction(const string &,bool (__cdecl T::* )(void))': could not deduce template argument for '__formal'
This is how you would use the method:
void DerivedClass::InitActions()
{
RegisterAction("general.copy", &DerivedClass::OnCopy);
RegisterAction("general.paste", &DerivedClass::OnPaste);
}
Btw, I can't use static_assert
because there I'm using c++14.
Doesn't anyone has any idea?
Upvotes: 1
Views: 116
Reputation: 347
When it is intended to use RegisterAction to register only for those classes that are derived from BaseClass, is seems that it is better to state with static_assert explicitly why some T cannot be used with RegisterAction instead of just "hiding" the problem with SFINAE.
So
template<class T>
void RegisterAction(const string& actionId, bool(T::*f)())
{
static_assert(
std::is_base_of<BaseClass, T>::value,
"T shall be derived from BaseClass for RegisterAction to work"
);
m_actions.emplace(actionId, std::bind(f, static_cast<T*>(this)));
}
will scream loudly and clearly about exact reason of why RegisterAction cannot accept some actions.
Upvotes: 0
Reputation: 119219
You're trying to introduce a new template parameter in order to cause a substitution error---which is correct---but your syntax is slightly incorrect. What you should write is:
template<class T, typename = std::enable_if_t<std::is_base_of<BaseClass, T>::value>>
// ^^^ this equal sign is crucial
When you write typename = foo
, you're declaring an unnamed type template parameter (it's like writing typename unused = foo
) and making the default value for that type foo
. Thus, if someone tries to instantiate this template with T
not derived from BaseClass
, a substitution failure occurs in the default argument, causing deduction to fail.
Since you wrote it without the equal sign, typename std::enable_if_t<...>
was interpreted as a typename-specifier, that is, the compiler thinks you're declaring a non-type template parameter whose type is typename std::enable_if_t<...>
, which you have left unnamed. Consequently, when T
is derived from BaseClass
, the type of this template parameter is void
. Since non-type template parameters cannot have type void
(as there are no values of type void
), a SFINAE error occurs here.
Interestingly, both GCC and Clang also fail to give a useful error message. They also complain that the unnamed template argument cannot be deduced, rather than pointing out that void
non-type template parameters are invalid (or even just pointing out that it is a non-type template parameter of type void
).
Upvotes: 2