27

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

当有人尝试这样做时,我想以某种方式获得编译时或运行时警告。

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

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

有什么诀窍吗?

4

6 回答 6

38
#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...
};
于 2013-10-03T08:15:41.027 回答
11

如果你有 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
}
于 2013-10-03T08:16:19.310 回答
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。

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

于 2013-10-03T09:38:38.010 回答
4

如果你没有 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仅可用于这些类型并中断其他类型。

于 2013-10-03T08:19:44.517 回答
1

更新Simple对较新的 C++20 标准更改的回答std::is_pod<T>将被弃用。由于这是谷歌这个主题中最明显的回应,让我描述一下其他人的不同之处,这些人将来到这里寻找最新的答案。

POD 类型被引入作为普通旧数据的定义 - 相当于 C 结构。从 C++11 到 C++20 对 POD 的要求是:

  • 是平凡的类型
    • 移动/复制/默认构造函数要么是微不足道的,要么是已删除的,并且至少存在一个。
    • 移动/复制赋值运算符也是如此
    • 所有成员(也是继承的)都是微不足道的
  • 是标准布局类型
    • 没有引用类型的成员
    • 没有虚拟基类(允许非虚拟继承)
    • 没有虚函数
    • 所有成员都具有相同的访问类型(公共/受保护/私有)
    • 所有成员(也继承)都是标准布局类型

对于那些不太了解用法之间区别的人,这里是经验法则。

  • std::is_trivial当您计划在对象上制作内存副本/移动时,应检查。它保证该对象的内存副本将创建精确的副本,不需要构造或解构。您可以分配内存并粘贴从套接字接收的内容。这是通过套接字传输数据或将它们存储在通用缓冲区中的基本用法。
  • std::is_standard_layout保证不同 C++ 标准之间的兼容性,因为有关内存对齐的规则随着时间的推移而发生变化,并且某些实现可能使用由一个标准版本保证的特性,而在另一个标准版本上则放宽了。差异与内存排序限制有关,例如每个下一个成员应该具有更高的内存地址或第一个成员应该具有整个结构的地址。
于 2020-01-11T09:52:30.350 回答
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
}
于 2013-10-03T08:20:40.553 回答