【问题标题】:Using std::enable_if with out-of-line member functions and templated static member conditions将 std::enable_if 与外联成员函数和模板化静态成员条件一起使用
【发布时间】:2020-12-17 20:14:43
【问题描述】:

我想使用 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 中的私有类型定义。它必须在类内。

如何在这里正确使用std::enable_if

【问题讨论】:

    标签: c++ templates c++17 sfinae enable-if


    【解决方案1】:

    鉴于当前的声明,似乎确实没有办法进行脱机定义(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 错误):

    #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)
    {
        // ...
    }
    

    使用小型转发内部函数委托给私有函数:

    #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>>
    {
        // ...
    }
    

    当然,只是内联定义它。

    【讨论】:

    • 您发布的第二个解决方案仅使用 GCC 编译,但失败并出现与使用 clang 的原始代码相同的错误。我已经尝试过使用static_assert,但随后我在容器代码中而不是在调用站点收到错误消息,这很烦人。第三种解决方案看起来不错。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-10-21
    • 2013-08-07
    相关资源
    最近更新 更多