将std :: enable_if与离线成员函数和模板化静态成员条件一起使用

问题描述

我想使用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>>
{
    // ...
}

当然,只是内联定义。