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:
#include <type_traits>
template <typename T>
struct Container {
template <typename Consumer>
static constexpr bool isConsumer = /* ... */;
template <typename Consumer>
void forEach(const Consumer &consumer);
};
template <typename T>
template <typename Consumer>
void Container<T>::forEach(const Consumer &consumer)
{
static_assert(isConsumer<Consumer>);
// ...
}
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):
#include <type_traits>
template <typename T>
struct Container {
template <typename Consumer>
static constexpr bool isConsumer = /* ... */;
template <typename Consumer, std::enable_if_t<Container<T>::template 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)
{
// ...
}
Delegate to a private function with a small forwarding inner function:
#include <type_traits>
template <typename T>
struct Container {
template <typename Consumer>
static constexpr bool isConsumer = /* ... */;
template <typename Consumer, std::enable_if_t<isConsumer<Consumer>, int> = 0>
void forEach(const Consumer &consumer) {
forEachImpl(consumer);
}
private:
template<typename Consumer>
void forEachImpl(const Consumer&);
};
template<typename T>
template<typename Consumer>
void Container<T>::forEachImpl(const Consumer& consumer) {
// ...
}
Use return type SFINAE instead so you are in the class's namespace for lookup:
#include <type_traits>
template <typename T>
struct Container {
template <typename Consumer>
static constexpr bool isConsumer = /* ... */;
template <typename Consumer>
std::enable_if_t<isConsumer<Consumer>> forEach(const Consumer &consumer);
};
template <typename T>
template <typename Consumer>
auto Container<T>::forEach(const Consumer &consumer) -> std::enable_if_t<isConsumer<Consumer>>
{
// ...
}
And of course, just defining it inline.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With