C++ Tutorial - Traits : A Template Specialization - 2020
The traits is a big topic which may require pages of tutorials. Here in this tutorial, however, we'll just get the brief introduction to traits.
Suppose we want to get max values for int or double, we do this:
#include <iostream> #include <climits> using namespace std; int main() { cout << "max values\n"; cout << "int: " << INT_MAX << endl; cout << "double: " << DBL_MAX << endl; return 0; }
And get the following output:
max values int: 2147483647 double: 1.79769e+308
Then, how about float or unsigned int?
If we do not remember the name for float, we have to look it up from somewhere to get the max value.
How about this?
#include <iostream> // #include <climits> #include <limits> using namespace std; int main() { cout << "max values\n"; cout << "int: " << numeric_limits<int>::max() << endl; cout << "double: " << numeric_limits<double>::max() << endl; cout << "float: " << numeric_limits<float>::max() << endl; cout << "unsigned int: " << numeric_limits<unsigned int>::max() << endl; return 0; }
Output:
max values int: 2147483647 double: 1.79769e+308 float: 3.40282e+038 unsigned int: 4294967295
We don't have to remember the name for our query, only the compiler needs to know the type.
In the code, we used the header <limits> instead of <climits>, and the query was, numeric_limits<type>::max().
What happened?
The numeric_limits template class replaced/supplemented the ordinary preprocessor constants of C (<climits> or <limits.h>).
Actually, in the limits.h, for example, DBL_MAX contains the "maximum value" trait for the double data type. However, by using a traits class such as numeric_limits, the type becomes part of the name, so that the maximum value for a double becomes numeric_limits<double>::max(), and also, we don't need to know the type.
Usually, we use templates to implement something once for any type. But we can also use templates to provide a common interface that is implemented for each type. We do this by providing specialization of a general template.
In this tutorial, we'll see the numeric_limits is the typical example of specialization:
Here is a general template that provides the default numeric values for any type:
namespace std { /* general numeric limits as default for any type */ template <typename T> class numeric_limits { public: // no specialization for numeric limit exists static const bool is_specialized = false; ... }; }
The general template of the numeric limits shown above is telling that there are no numeric limits available for type T by setting the member is_specialized to false.
We can write a specialized version of the template as below:
namespace std { /* setting numeric limits for int type */ template <> class numeric_limits<int> { public: // Now we have a specialization for numeric limit for int. // It does exists. static const bool is_specialized = true; static int min() throw() { return -2147483648; } static int max() throw() { return 2147483647; } static const int digits = 31; ... }; }
The general numeric_limits template and its standard specialization are provided in the header file <limits>. The specialization are provided for any fundamental type that can represent numeric values: bool, char, signed char, unsigned char, wchar_t, short, unsigned short, int, unsigned int, long, unsigned long, float, double, and long double. They can be supplemented easily for user-defined numeric types.
#include <iostream> #include <limits> using namespace std; int main() { cout << "is_signed(char): " << numeric_limits<char>::is_signed << endl; cout << "is_specialized(long double): " << numeric_limits<long double>::is_specialized << endl; cout << "is_specialized(std::string): " << numeric_limits<std::string>::is_specialized << endl; return 0; }
If we run the code, the std::string gives false for is_specialized as expected:
is_signed(char): 1 is_specialized(long double): 1 is_specialized(std::string): 0
So, what is traits?
Traits gives us additional information other than just the type. More exactly, we can get some information about a type.
That's what traits let you do: they allow you to get information about a type during compilation. ...The fact that traits must work with built-in types means that things like nesting information inside types won't do, because there's no way to nest information inside pointers. The traits information for a type, then must be external to the type. The standard technique is to put it into a template and cone or more specializations of that template.
...By convention, traits are always implemented as structs. Another convention is that the structs used to implement traits are knows as trait classes.
-Scott Meyers #Item 47: "Use traits classes for information about types" of his book Effective C++ 55 Specific Ways to Improve Your Programs and Designs.
Traits of std::iterator, please visit Templates.
For template specialization, please visit Template Specialization.
Ph.D. / Golden Gate Ave, San Francisco / Seoul National Univ / Carnegie Mellon / UC Berkeley / DevOps / Deep Learning / Visualization