我有一个 c++ 模板类,它只有在模板化类型是普通旧数据时才能正确运行。任何带有构造函数的东西都不能正常工作。
当有人尝试这样做时,我想以某种方式获得编译时或运行时警告。
//this should generate error
myclass<std::string> a;
//this should be fine
myclass<int> b;
有什么诀窍吗?
我有一个 c++ 模板类,它只有在模板化类型是普通旧数据时才能正确运行。任何带有构造函数的东西都不能正常工作。
当有人尝试这样做时,我想以某种方式获得编译时或运行时警告。
//this should generate error
myclass<std::string> a;
//this should be fine
myclass<int> b;
有什么诀窍吗?
#include <type_traits>
template<typename T>
class myclass
{
static_assert(std::is_pod<T>::value, "T must be POD");
// stuff here...
};
如果您将非 POD 类型作为模板参数传递,上述将导致编译错误。此解决方案需要 C++11 作为<type_traits>
标头和static_assert
关键字。
编辑:如果您的编译器支持 TR1(大多数都支持),您也可以在 C++03 中实现它:
#include <tr1/type_traits>
template<typename T>
class myclass
{
static char T_must_be_pod[std::tr1::is_pod<T>::value ? 1 : -1];
// stuff here...
};
如果你有 C++11 支持 std::is_pod 应该完全符合你的需要。将它与 std::enable_if 或标签调度一起使用。例如这样的:
template <typename T, typename Enable = void>
class Test;
template<typename T>
class Test<T, typename std::enable_if<std::is_pod<T>::value, void>::type>
{};
int main() {
Test<int> t1;
//Test<std::string> t2; <-this will not compile
}
虽然static_assert
在大多数情况下可能就足够了,enable_if
但通过 SFINAE 的方式使用和标记调度为您的类的用户提供了更大的灵活性。考虑:
#include <type_traits>
#include <string>
#include <iostream>
template <typename T,
class=typename std::enable_if< std::is_pod<T>::value >::type>
struct myclass
{
typedef T value_type;
T data;
};
template <typename T>
void enjoy(T)
{
std::cout << "Enjoying T!" << std::endl;
}
template <typename T>
void enjoy(typename myclass<T>::value_type)
{
std::cout << "Enjoying myclass<T>::value_type!" << std::endl;
}
int main()
{
enjoy<int>(int()); // prints: Enjoying myclass<T>::value_type!
enjoy<std::string>(std::string()); // SFINAE at work - prints: enjoying T!
myclass<int> i; // compiles OK
//myclass<std::string> s; // won't compile - explicit instantiation w/non-POD!
}
现在,如果您从myclass
定义中删除第二个模板参数,而是像其他人建议的那样,添加一个
static_assert(std::is_pod<T>::value, "POD expected for T");
在类中,第二行将main()
无法编译,触发 static_assert。
也就是说static_assert
,相比失败的错误,来自的错误对人类观察者更友好enable_if
。所以,如果static_assert
适合你,那就去吧。否则,如果您确实需要对类周围的通用编程更加友好,请考虑在以下位置添加解释性注释enable_if
:
// POD expected for T
class=typename std::enable_if< std::is_pod<T>::value >::type>
除非你周围的每个人都精通 C++11。
在现实生活中,解释为什么T 必须是static_assert
评论文本和评论文本的 POD 是一个好主意。
如果你没有 C++11
如果目标 POD 类型有限(int
, float
, ...),您可以将实现放入.cpp
文件并为该类型显式实例化它:
.h
文件:
template <typename T>
class myclass
{
T data;
public:
void func();
};
.cpp
文件:
#include "myclass.h"
template <typename T>
void myclass<T>::func()
{
}
template class myclass<float>;
template class myclass<int>;
template class myclass<char>;
...
之后,myclass
仅可用于这些类型并中断其他类型。
更新Simple对较新的 C++20 标准更改的回答std::is_pod<T>
将被弃用。由于这是谷歌这个主题中最明显的回应,让我描述一下其他人的不同之处,这些人将来到这里寻找最新的答案。
POD 类型被引入作为普通旧数据的定义 - 相当于 C 结构。从 C++11 到 C++20 对 POD 的要求是:
对于那些不太了解用法之间区别的人,这里是经验法则。
std::is_trivial
当您计划在对象上制作内存副本/移动时,应检查。它保证该对象的内存副本将创建精确的副本,不需要构造或解构。您可以分配内存并粘贴从套接字接收的内容。这是通过套接字传输数据或将它们存储在通用缓冲区中的基本用法。std::is_standard_layout
保证不同 C++ 标准之间的兼容性,因为有关内存对齐的规则随着时间的推移而发生变化,并且某些实现可能使用由一个标准版本保证的特性,而在另一个标准版本上则放宽了。差异与内存排序限制有关,例如每个下一个成员应该具有更高的内存地址或第一个成员应该具有整个结构的地址。使用 type_traits 和 static_assert,这很容易:
#include <type_traits>
struct A{
};
struct B{
virtual ~B(){}
};
template< class T >
struct MyClass
{
static_assert( std::is_pod<T>::value, "not a POD" );
};
int main()
{
MyClass<A> a;
//MyClass<B> b; -- break, cause not a POD
}