Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

restricting c++ template usage to POD types

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?

like image 348
Lucas Meijer Avatar asked Oct 03 '13 08:10

Lucas Meijer


People also ask

How will you restrict the template for a specific datatype?

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.

Can template be used for user defined data 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.

Can we use template in C?

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.

Are C++ templates type safe?

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.


5 Answers

#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...
};
like image 90
Simple Avatar answered Oct 19 '22 23:10

Simple


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
}
like image 32
magor Avatar answered Oct 19 '22 23:10

magor


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.

like image 6
Vassilii Khachaturov Avatar answered Oct 20 '22 01:10

Vassilii Khachaturov


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.

like image 4
masoud Avatar answered Oct 20 '22 01:10

masoud


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.
like image 1
Maciej Załucki Avatar answered Oct 20 '22 01:10

Maciej Załucki