I want to use SFINAE to create a templated member function which takes a Consumer
functor. Whether something is a consumer depends on a templated static constexpr bool isConsumer
member variable. I have simplified my code down to the following example:
#include <type_traits>
template <typename T>
struct Container {
T data[100];
template <typename Consumer>
static constexpr bool isConsumer = std::is_invocable_r_v<void, Consumer, T>;
template <typename Consumer, std::enable_if_t<isConsumer<Consumer>, int> = 0>
void forEach(const Consumer &consumer);
};
template <typename T>
template <typename Consumer, std::enable_if_t<Container<T>::template isConsumer<Consumer>, int>>
void Container<T>::forEach(const Consumer &consumer)
{
for (int i = 0; i < 100; ++i) {
consumer(data[i]);
}
}
This does not compile for reasons that I don't comprehend:
<source>:16:20: error: out-of-line definition of 'forEach' does not match any declaration in 'Container<T>'
void Container<T>::forEach(const Consumer &consumer)
^~~~~~~
It does compile just fine when I inline isConsumer
, as in, use std::is_invocable_r_v
directly. I would like to avoid this because in my real code the signature of the Consumer
is quite complicated and this requires quite a bit of copy/paste.
Pulling isConsumer
outside the class is not an option either, because in my real code it depends on private typedefs inside Container
. It must be inside the class.
How do I use std::enable_if
correctly here?
It seems that there really is no way to make an out-of-line definition given the current declaration (gcc complains that the declaration uses an "anonymous type")
Possible workarounds:
Use a
static_assert
instead of SFINAE:Fully qualify the declaration too so the definition so they should refer to the same type (This doesn't work in clang. Seems like a clang bug):
Delegate to a private function with a small forwarding inner function:
Use return type SFINAE instead so you are in the class's namespace for lookup:
And of course, just defining it inline.