首页 > 解决方案 > 如何专门化容器和枚举的模板

问题描述

我正在尝试为枚举类型和 stl 容器类型专门提供一个简单的功能。SFINAE 的想法适用于使用 enable_if 的枚举,但是类似的 stl 容器技术不起作用(我知道依赖 value_type 的存在并假设它是容器不是一个好主意,但这不是重点)。

template <typename T, typename = void>
    struct wrapper {
        static T getValue()
        {
            std::cout<<"\n WRAPPER DEFAULT VERSION IS CALLED.\n";
            return T();
        }
    };

    template<typename T>
    struct wrapper<T,typename std::enable_if<std::is_enum<T>::value>::type>{
        static T getValue()
        {
            std::cout<<"\n WRAPPER ENUM VERSION IS CALLED.\n";
            return T();
        }
    };

    template<typename Container>
    struct wrapper<Container, typename Container::value_type> {
        static Container getValue()
        {
            std::cout<<"\n WRAPPER CONTAINER VERSION IS CALLED.\n";
            return Container();
        }
    };


int main()
{
    //En is an enum type
    En en = (En) wrapper<En>::getValue(); //Prints ENUM VERSION

    std::vector<int> vec;
    vec = wrapper<std::vector<int>>::getValue(); //Prints DEFAULT VERSION
}

请让我知道为什么第二次调用会转到默认实现?

解决方案:

感谢 Sam Varshavchik,我发现我错过了第二个参数应该解析为 void 的点(就像在 enable_if::type 的情况下一样),否则我必须显式传递第二个参数才能使我的调用解析为容器版本: wrapper<std::vector<int>, int>::getValue();

为了使原始版本正常工作(在 void_t 不可用的 C++ 17 之前),我正在创建自己的类型特征,这依赖于容器为它们定义了迭代器类型的事实:

    template <typename T1, typename T2 = void>
    struct container_trait
    {};

    template <typename T1>
    struct container_trait<T1, typename T1::iterator> {
        typedef void type;
    };

现在我的容器版本的包装器变为:

    template<typename Container>
    struct wrapper<Container, typename container_trait<Container, typename Container::iterator>::type> {
        static Container getValue(const rapidjson::Value& rjv)
        {
            std::cout<<"\n WRAPPER CONTAINER VERSION IS CALLED.\n";
            return Container();
        }
    };

现在同样的调用工作得很好:

vec = wrapper<std::vector<int>>::getValue(); //Prints CONTAINER VERSION

标签: c++templatessfinae

解决方案


第二次调用默认实现的原因很简单。

只需手动计算如何为容器版本模板的参数推导参数:

template<typename Container>
struct wrapper<Container, typename Container::value_type>

您正在实例化以下模板:

wrapper<std::vector<int>>

所以:

1)Container是一个std::vector<int>

2)Container::value_typeint

因此,该专业化变为:

struct wrapper<std::vector<int>, int>

但是,您只调用:

wrapper<std::vector<int>, void>

因为void是第二个模板参数的默认值,所以这匹配了错误的特化。

解决方案很简单,容器专业化应该是:

#include <type_traits>

template<typename Container>
struct wrapper<Container, std::void_t<typename Container::value_type>> {

std::void_t是 C++17,stackoverflow.com 上还有其他问题解释了如何为早期的 C++ 标准实现它。完整示例:

#include <vector>
#include <iostream>
#include <type_traits>

enum En {};

template <typename T, typename = void>
struct wrapper {
        static T getValue()
        {
        std::cout<<"\n WRAPPER DEFAULT VERSION IS CALLED.\n";
        return T();
        }
};

template<typename T>
struct wrapper<T,typename std::enable_if<std::is_enum<T>::value>::type>{
        static T getValue()
        {
        std::cout<<"\n WRAPPER ENUM VERSION IS CALLED.\n";
        return T();
        }
};

template<typename Container>
struct wrapper<Container, std::void_t<typename Container::value_type>> {
        static Container getValue()
        {
        std::cout<<"\n WRAPPER CONTAINER VERSION IS CALLED.\n";
        return Container();
        }
};


int main()
{
    //En is an enum type
    En en = (En) wrapper<En>::getValue(); //Prints ENUM VERSION

    std::vector<int> vec;
    vec = wrapper<std::vector<int>>::getValue(); //Prints DEFAULT VERSION
}

结果:

调用了包装器枚举版本。

包装容器版本被调用。


推荐阅读