将c ++模板使用限制为POD类型

时间:2013-10-03 08:14:08

标签: c++ templates typetraits

我有一个c ++模板类,只有在模板化类型是普通旧数据时才能正常运行。任何带有构造函数的东西都无法正常工作。

无论如何,当有人试图这样做时,我想以某种方式得到编译时或运行时警告。

//this should generate error
myclass<std::string> a;

//this should be fine
myclass<int> b;

这样做有诀窍吗?

6 个答案:

答案 0 :(得分:36)

#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...
};

答案 1 :(得分:10)

如果你有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
}

答案 2 :(得分:6)

虽然在大多数情况下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流利的。

在现实生活中,为static_assert和评论文本解释为什么 T必须是POD是一个好主意。

答案 3 :(得分:4)

如果您还没有C ++ 11

如果目标POD类型有限(intfloat,...)您可以将实现放入.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只适用于那些类型,而其他类型则可用。

答案 4 :(得分:0)

使用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
}

答案 5 :(得分:0)

Simple's的答案更新为newer C++20 standard changesstd::is_pod<T>将被弃用。由于这是Google在本主题中最明显的回应,因此,我将介绍其他人的不同之处,以期找到最新的答案。

引入

POD type 作为普通旧数据的定义-等同于C结构。从C ++ 11到C ++ 20的POD要求是:

  • 是平凡的类型
    • 移动/复制/默认构造器为trivial或已删除,并且每个构造器中至少有一个存在。
    • 移动/复制分配运算符相同
    • 所有成员(也是继承的)都是琐碎的
  • 是标准布局类型
    • 没有引用类型的成员
    • 没有虚拟基类(允许非虚拟继承)
    • 没有虚拟功能
    • 所有成员都具有相同的访问类型(公共/受保护/私有)
    • 所有成员(也都是继承的)都是标准布局类型

对于那些真的不知道用法之间有什么区别的人,这是经验法则。

    计划在对象上进行内存复制/移动时,应检查
  • std::is_trivial。它保证了该对象的内存副本将创建精确的副本,不需要构造或解构。您可以分配内存并粘贴从套接字接收的内容。这是通过套接字传输数据或将其存储在通用缓冲区中时的基本用法。
  • std::is_standard_layout可以保证不同的C ++标准之间的兼容性,因为有关内存对齐的规则会随着时间而改变,并且某些实现可能会使用一种标准版本所保证的功能,而另一种标准版本则放宽了这些功能。差异与内存排序限制有关,例如每个下一个成员应具有更高的内存地址或第一个成员应具有整个结构的地址。