Question

I have a c++ template class, which only operates correctly if the templatized type is plain old data. Anything with a constructor that does anything will not work correctly.

I'd like to somehow get a compiletime or runtime warning when someone tries to do so anyway.

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

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

is there a trick to do this?

Was it helpful?

Solution

#include <type_traits>

template<typename T>
class myclass
{
    static_assert(std::is_pod<T>::value, "T must be POD");

    // stuff here...
};

The above will cause a compilation error if you pass a non-POD type as the template parameter. This solution requires C++11 for the <type_traits> header and static_assert keyword.

EDIT: You can also implement this in C++03 if your compiler supports TR1 (most do):

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

OTHER TIPS

If you have C++11 support std::is_pod should do exactly what you need. Use it with std::enable_if or with tag dispatch. For example something like this:

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
}

While the static_assert probably suffices in most cases, using enable_if and tag dispatch gives greater flexibility to the users of your class by the ways of SFINAE. Consider:

#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!
}

Now if you remove the 2nd template argument from myclass definition, and instead, like others have suggested, add a

  static_assert(std::is_pod<T>::value, "POD expected for T");

inside the class, the second line in main() will just fail to compile, triggering the static_assert.

That said, the errors from static_assert are much more friendly to human observer, than those from the failed enable_if. So, if static_assert works for you, go for it. Otherwise, if you do need to be friendlier to generic programming around your class, consider adding an explanatory comment around enable_if:

 // POD expected for T
 class=typename std::enable_if< std::is_pod<T>::value >::type>

unless everyone around you is C++11-fluent.

In real life, it's a good idea to explain why T must be POD both for static_assert and for the comment texts.

If you have not C++11

If the targeted POD types are limited (int, float, ...) You can put the implementation into a .cpp file and explicit instantiate it for that types:

.h file:

template <typename T>
class myclass
{
    T data;
public:
    void func();
};

.cpp file:

#include "myclass.h"

template <typename T>
void myclass<T>::func()
{
}

template class myclass<float>;
template class myclass<int>;
template class myclass<char>;
...

After that, myclass is just usable for those types and breaks for other.

Updating Simple's answer to newer C++20 standard changes, std::is_pod<T> will be deprecated. As this is most visible response in this topic in google, let me describe differences for others that will come here looking for up to date answer.

POD type was introduced as definition of Plain Old Data - equivalent of C structures. Requirements of POD since C++11 until C++20 are:

  • Is Trivial Type
    • Move/copy/default constructors are either trivial or deleted AND at least one of each exists.
    • The same for move/copy assignment operators
    • All members (also inherited) are trivial
  • Is Standard Layout Type
    • Has no members of reference type
    • Does not have virtual base class (non-virtual inheritance is allowed)
    • Does not have virtual functions
    • All members have the same access type (public/protected/private)
    • All members (also inherited) are standard layout types

For those of you who don't really know the difference between usages, here's the rule of thumb.

  • std::is_trivial should be checked when you plan to make memory copies/movements on your object. It guarantees that memory copy of this object will create exact copy, does not require construction or deconstruction. You can allocate memory and paste content received from socket. That's basic usage while transferring data over sockets or storing them in generic buffers.
  • std::is_standard_layout guarantees compatibility between different C++ standards as rules regarding memory alignment were changed over time and some implementations may use features guaranteed by one standard version which are relaxed on other one. Differences are related to memory ordering restrictions like each next member should have higher memory address or first member should have address of whole structure.

With type_traits, and static_assert, it is quite easy :

#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
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top