如何专门化容器和枚举的模板
How to specialize template for containers and enums
我正在尝试专门针对枚举类型和 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();
为了使原始版本工作(在 C++ 17 之前 void_t 不可用),我正在创建我自己的类型特征,这依赖于容器具有为它们定义的迭代器类型这一事实:
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
第二次调用默认实现的原因很简单
简单地手工计算出容器版本模板的参数是如何推导出来的:
template<typename Container>
struct wrapper<Container, typename Container::value_type>
您正在实例化以下模板:
wrapper<std::vector<int>>
所以:
1) Container
是一个 std::vector<int>
2) Container::value_type
是 int
因此这个专业变成:
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,whosebug.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
}
结果:
WRAPPER ENUM VERSION IS CALLED.
WRAPPER CONTAINER VERSION IS CALLED.
我正在尝试专门针对枚举类型和 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();
为了使原始版本工作(在 C++ 17 之前 void_t 不可用),我正在创建我自己的类型特征,这依赖于容器具有为它们定义的迭代器类型这一事实:
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
第二次调用默认实现的原因很简单
简单地手工计算出容器版本模板的参数是如何推导出来的:
template<typename Container>
struct wrapper<Container, typename Container::value_type>
您正在实例化以下模板:
wrapper<std::vector<int>>
所以:
1) Container
是一个 std::vector<int>
2) Container::value_type
是 int
因此这个专业变成:
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,whosebug.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
}
结果:
WRAPPER ENUM VERSION IS CALLED.
WRAPPER CONTAINER VERSION IS CALLED.