问题描述
我想使用SFINAE创建一个带有Consumer
仿函数的模板化成员函数。某物是否是消费者取决于模板化的static constexpr bool isConsumer
成员变量。我将代码简化为以下示例:
#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]);
}
}
由于我不理解的原因而无法编译:
<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)
^~~~~~~
当我内联isConsumer
时,它的编译效果很好,例如,直接使用std::is_invocable_r_v
。我想避免这种情况,因为在我的真实代码中,Consumer
的签名非常复杂,并且需要大量复制/粘贴。
也不可以将isConsumer
拉到类之外,因为在我的真实代码中,它依赖于Container
内部的私有typedef。它必须在类中。
如何在此处正确使用std::enable_if
?
解决方法
在给定当前声明的情况下,似乎真的没有办法做出离线定义(gcc抱怨声明使用了“匿名类型”)
可能的解决方法:
使用static_assert
代替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>);
// ...
}
也完全限定了声明,因此定义也应使用相同的类型(在clang中不起作用。似乎是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,int>>
void Container<T>::forEach(const Consumer &consumer)
{
// ...
}
使用小的转发内部功能委托给私有功能:
#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) {
// ...
}
使用返回类型SFINAE代替,这样您就可以在类的命名空间中进行查找:
#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>>
{
// ...
}
当然,只是内联定义。