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?
There are ways to restrict the types you can use inside a template you write by using specific typedefs inside your template. This will ensure that the compilation of the template specialisation for a type that does not include that particular typedef will fail, so you can selectively support/not support certain types.
Template in C++is a feature. We write code once and use it for any data type including user defined data types. For example, sort() can be written and used to sort any data type items. A class stack can be created that can be used as a stack of any data type.
The main type of templates that can be implemented in C are static templates. Static templates are created at compile time and do not perform runtime checks on sizes, because they shift that responsibility to the compiler.
C++ templates are checked at least twice. First, when a template is declared & defined, second when it is instantiated. After a template successfully instantiated it is in a type safe state.
#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...
};
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:
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.If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With