Why can I not use enable_if in the following context?
I'd like to detect whether my templated object has the member function notify_exit
template <typename Queue>
class MyQueue
{
public:
auto notify_exit() -> typename std::enable_if<
has_member_function_notify_exit<Queue, void>::value,
void
>::type;
Queue queue_a;
};
Initialised with:
MyQueue<std::queue<int>> queue_a;
I keep getting (clang 6):
example.cpp:33:17: error: failed requirement 'has_member_function_notify_exit<queue<int, deque<int, allocator<int> > >, void>::value';
'enable_if' cannot be used to disable this declaration
has_member_function_notify_exit<Queue, void>::value,
or (g++ 5.4):
In instantiation of 'class MyQueue<std::queue<int> >':
33:35: required from here
22:14: error: no type named 'type' in 'struct std::enable_if<false, void>'
I've tried a bunch of different things, but can't figure out why I can't use enable_if to disable this function. Isn't this exactly what enable_if is for?
I've put a full example here (and cpp.sh link that often fails)
I've found similar Q/As on SO, but generally those were more complicated and attempting something different.
When you instantiate MyQueue<std::queue<int>> the template argument std::queue<int> gets substituted into the class template. In the member function declaration that leads to a use of typename std::enable_if<false, void>::type which does not exist. That's an error. You can't declare a function using a type that doesn't exist.
Correct uses of enable_if must depend on a template parameter that is deduced. During template argument deduction, if substituting the deduced template argument for the template parameter fails (i.e. a "substitution failure") then you don't get an immediate error, it just causes deduction to fail. If deduction fails, the function isn't a candidate for overload resolution (but any other overloads will still be considered).
But in your case the template argument is not deduced when calling the function, it's already known because it comes from the surrounding class template. That means that substitution failure is an error, because the function's declaration is ill-formed before you even try to perform overload resolution to call it.
You can fix your example by turning the function into a function template, so it has a template parameter that must be deduced:
template<typename T = Queue>
auto notify_exit() -> typename std::enable_if<
has_member_function_notify_exit<T, void>::value,
void
>::type;
Here the enable_if condition depends on T instead of Queue, so whether the ::type member exists or not isn't known until you try to substitute a template argument for T. The function template has a default template argument, so that if you just call notify_exit() without any template argument list, it's equivalent to notify_exit<Queue>(), which means the enable_if condition depends on Queue, as you originally wanted.
This function can be misused, as callers could invoke it as notify_exit<SomeOtherType>() to trick the enable_if condition into depending on the wrong type. If callers do that they deserve to get compilation errors.
Another way to make the code work would be to have a partial specialization of the entire class template, to simply remove the function when it's not wanted:
template <typename Queue,
bool Notifiable
= has_member_function_notify_exit<Queue, void>::value>
class MyQueue
{
public:
void notify_exit();
Queue queue_a;
};
// partial specialization for queues without a notify_exit member:
template <typename Queue>
class MyQueue<Queue, false>
{
public:
Queue queue_a;
};
You can avoid repeating the whole class definition twice in a few different ways. You could either hoist all the common code into a base class and only have the notify_exit() member added in the derived class that depends on it. Alternatively you can move just the conditional part into a base class, for example:
template <typename Queue,
bool Notifiable
= has_member_function_notify_exit<Queue, void>::value>
class MyQueueBase
{
public:
void notify_exit();
};
// partial specialization for queues without a notify_exit member:
template <typename Queue>
class MyQueueBase<Queue, false>
{ };
template<typename Queue>
class MyQueue : public MyQueueBase<Queue>
{
public:
// rest of the class ...
Queue queue_a;
};
template<typename Queue, bool Notifiable>
void MyQueueBase<Queue, Notifiable>::notify_exit()
{
static_cast<MyQueue<Queue>*>(this)->queue_a.notify_exit();
}
With C++20 and concept, you may use requires:
void notify_exit() requires has_member_function_notify_exit<Queue, void>::value;
Instantiating a template causes the member instantiation of all the declarations it contains. The declaration you provide is simply ill-formed at that point. Furthermore, SFINAE doesn't apply here, since we aren't resolving overloads when the class template is instantiated.
You need to make the member into something with a valid declaration and also make sure the check is delayed until overload resolution. We can do both by making notify_exit a template itself:
template<typename Q = Queue>
auto notify_exit() -> typename std::enable_if<
has_member_function_notify_exit<Q, void>::value,
void
>::type;
A working cpp.sh example
Related
I know that a C++ compiler picks a template specialization in preference to the primary template:
template<class T, class Enable = void>
class A {}; // primary template
template<class T>
class A<T, std::enable_if_t<std::is_floating_point_v<T>, void>> {
}; // specialization for floating point types
However, I don't understand why the selection fails when a type other than void is used as argument to enable_if:
template<class T>
class A<T, std::enable_if_t<std::is_floating_point_v<T>, int>> {
}; // specialization for floating point types
The compiler would surely "see" class A<T, int>.
So it certainly can't be SFINAE, nevertheless the primary template is preferred to the specialization.
This question arises from a context where a custom type detection machinery could be used instead of enable_if, e.g. an SFINAE friendly type extractor like common_type.
When you have
A<some_floating_point_type> some_name;
The template parameters are some_floating_point_type and the implicit void. When the compiler instantiates
template<class T>
class A<T, std::enable_if_t<std::is_floating_point_v<T>, int>> {
}; // specialization for floating point types
it would get A<some_floating_point_type, int>, which does not match the A<some_floating_point_type, void> type that some_name has. Because of that, the specialization is ignored and you get the primary template. You can verify this by trying to create a A<some_floating_point_type, int> and you'll get that the specialization was picked.
I find it helpful to think of a specialization as an alternate recipe. First the template arguments are deduced, and then if they match any of the specializations, then we switch to using that alternate recipe. If not, then the original recipe is used.
Specializations are irrelevant until the compiler knows which types it is going to use for the primary template.
When you write A<double>, then the compiler looks only at the primary template and sees that you actually mean A<double,void>.
And only then it is looking for specializations. Now, when your specialization is for A<double,int>, then it is not suitable because you asked for A<double,void>.
In simple terms, this
template<class T, class Enable = void>
class A {};
says "A is a template, it has two arguments, the second has a default that is void". Thats the primary template. When you explicitly provide only one parameter then the second argument is void.
Now the specialization:
template<class T>
class A<T, std::enable_if_t<std::is_floating_point_v<T>, int>> {
};
This doesn't change the above. A is still a template with 2 arguments and when the second one is not specified then it is void. In simple terms it either means "substituting T in std::enable_if_t<std::is_floating_point_v<T> is a failure, because the type alias does not exist" when the condition is false, SFINAE kicks in and the specialization is ignored. When the condition is true: "Whenever A is instantiated with arguments T (some type) and int then use this definition".
When you instantiate A<int> then the second template argument is void. The specialization does not change that. That is A<int> is actually A<int,void>. It does not match the specialiazation.
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?
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).
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.
I've got a template class with some method, say, foo. I want to specify a default behavior for this method for all POD types and introduce separate specializations for other types. (The real problem is more complicated but this is an MWE.) I tried to do it with SFINAE in a usual way.
template<typename T>
class C {
public:
void foo(T t);
};
template<typename T>
typename std::enable_if<
std::is_pod<T>::value,
void>::type
C<T>::foo(T t) {
// do something
}
Even with this code (i.e. not making any instances of C) I've got an error:
prototype for ‘typename std::enable_if<std::is_pod<_Tp>::value, void>::type C<T>::foo(T)’ does not match any in class ‘C<T>’
This seems strange for me, because either both method types are void or the second one is eliminated by SFINAE.
What is more weird, if I replace a condition within enable_if with false, I got an error which shows that SFINAE doesn't work at all:
error: ‘type’ in ‘struct std::enable_if<false, void>’ does not name a type
Where am I wrong?
For SFINAE to apply the identifier must be a template and substitution has to be involved.
First case fails because foo is not a template.
Second case fails for same reason and because no substitution is involved.
I've been trying more about multi threaded programming in c++, and i was having difficulty understanding std::promise so i began searching for answers on this website, and low and behold, there is somebody with the same question as me. But reading the answer made me even more confused
this is the code in the answer that presumably is a similar implementation of std::packaged_task
template <typename> class my_task;
template <typename R, typename ...Args>
class my_task<R(Args...)>
{
std::function<R(Args...)> fn;
std::promise<R> pr; // the promise of the result
public:
template <typename ...Ts>
explicit my_task(Ts &&... ts) : fn(std::forward<Ts>(ts)...) { }
template <typename ...Ts>
void operator()(Ts &&... ts)
{
pr.set_value(fn(std::forward<Ts>(ts)...)); // fulfill the promise
}
std::future<R> get_future() { return pr.get_future(); }
// disable copy, default move
};
in this code,
1- what does this syntax mean template <typename R, typename ...Args> class my_task<R(Args...)>, more specifically, what is the purpose of <R(Args...)> ?
2- why is there a foroward decleration for the class?
thanks
There was some brief discussion in the comments how 1 and 2 should be two separate questions, but I believe that they both are just two sides to the same exact question, for the following reasons:
template <typename> class my_task;
template <typename R, typename ...Args>
class my_task<R(Args...)>; ....
The first statement declares a template that takes a typename as its sole template parameter. The second statement declares a specialization for that template class.
In this context:
R(Args...)
Will specialize for any typename that matches a function. This template specialization will match any template instantiation that passes a function signature for a typename. Barring any problems within the template itself, this template specialization will be used for:
my_task<int (const char *)>
or, a function that takes a const char * parameter and returns an int. The template specialization will also match:
my_task<Tptr *(Tptr **, int)>
or, a function that takes two parameters, Tptr ** and an int, and returns a Tptr * (here, Tptr is some other class).
The template specialization will NOT match:
my_task<int>
Or
my_task<char *>
Because they are not function signatures. If you try to instantiate this template using a non-function typename you're going to get a compilation error. Why?
Well, that's because the template is not defined:
template<typename> class my_task;
Don't think of this as just a forward declaration. it's a forward declaration of a template that takes a template parameter, and the template will not be defined anywhere. Rather, the template declaration allows for a subsequent template specialization declaration, that will match only specific types passed as a template parameter.
This is a common programming technique for restricting the kinds of typenames or classes that can be used with a particular template. Instead of allowing a template to be used with just any typename or class, the template can only be used with some subset. In this case, a function typename, or signature.
It also makes it easier for the template itself to explicitly reference -- in this case -- to the template parameter's return type, and the parameter types. If the template has just a bland, single typename as a template parameter, it can't easily access the function's return type, or the function parameter's types.
1: What does this syntax mean template <typename R, typename ...Args> class my_task<R(Args...)>
This is a specialization of the class template my_task. The <R(Args...)> after the name means it is specialized for that type, and that type is a function. R(Args...) is the type of a function taking Args parameters and returning R. So, my_task<void()> mt; for example would make Args be an empty parameter pack, and R would be void.
2: Why is there a forward declaration for the class?
The class is declared, but unlike an ordinary forward declaration, the un-specialized version isn't defined. This class is only intended to work when the type is a function, so if someone tries to use something that isn't a function (like my_task<int>), it will give an error about the type being undefined.
my_task<void*(int, int)> mt1; //R = void*, Args = int, int
my_task<int> mt2; //error: use of undefined class
This is my code to check whether class has member function begin or not :
template<typename T> struct has_begin
{
struct dummy {typedef void const_iterator;};
typedef typename std::conditional< has_iterator<T>::yes, T, dummy>::type TType;
typedef typename TType::const_iterator Iter;
struct fallBack{ Iter begin() const ; Iter end() const;};
struct checker : T, fallBack {};
template <typename B, B> struct cht;
template<typename C> static char check(cht< Iter (fallBack::*)() const, &C::begin>*); // problem is here
template<typename C> static char (&check(...))[2];
public:
enum {no = (sizeof(check<checker>(0))==sizeof(char)),
yes=!no};
};
If I change second argument of cht in check(cht< Iter (fallBack::*)() const, &C::begin>*); to
&checker::begin , This doesn't changes the semantic of code since cht's second template argument is always checker due to this enum {no = (sizeof(check<checker>(0))==sizeof(char))
but code change results in error now which are :
prog.cpp: In instantiation of 'has_begin<std::vector<int> >':
prog.cpp:31:51: instantiated from here
prog.cpp:23:38: error: reference to 'has_begin<std::vector<int> >::checker::begin' is ambiguous
I want to know what is the reason behind this behavior.
from the Wikipedia article about SFINAE - Substitution Failure is Not An Error:
[...] when creating a candidate set for overload resolution, some (or all)
candidates of that set may be the result of substituting deduced
template arguments for the template parameters. If an error occurs
during substitution, the compiler removes the potential overload from
the candidate set instead of stopping with a compilation error [...]
In your code as posted, an ambiguity error occurs while instantiating the function template check with parameter C == typename has_begin<T>::checker, and that substitution leads to the error, so the instantiation is simply removed from the overload set.
If you change your code, a similar ambiguaty error occurs with &checker::begin.
This time, however, it is not the result of substituting the template parameter C for the check function template. The subsitution of the template parameter T of struct has_begin is not relevant for the SFINAE rule, as that template has already been successfully instantiated.