How Do Traits Classes Work and What Do They Do

How do traits classes work and what do they do?

Perhaps you’re expecting some kind of magic that makes type traits work. In that case, be disappointed – there is no magic. Type traits are manually defined for each type. For example, consider iterator_traits, which provides typedefs (e.g. value_type) for iterators.

Using them, you can write

iterator_traits<vector<int>::iterator>::value_type x;
iterator_traits<int*>::value_type y;
// `x` and `y` have type int.

But to make this work, there is actually an explicit definition somewhere in the <iterator> header, which reads something like this:

template <typename T>
struct iterator_traits<T*> {
typedef T value_type;
// …
};

This is a partial specialization of the iterator_traits type for types of the form T*, i.e. pointers of some generic type.

In the same vein, iterator_traits are specialized for other iterators, e.g. typename vector<T>::iterator.

What is the difference between a trait and a policy?

Policies

Policies are classes (or class templates) to inject behavior into a parent class, typically through inheritance. Through decomposing a parent interface into orthogonal (independent) dimensions, policy classes form the building blocks of more complex interfaces. An often seen pattern is to supply policies as user-definable template (or template-template) parameters with a library-supplied default. An example from the Standard Library are the Allocators, which are policy template parameters of all STL containers

template<class T, class Allocator = std::allocator<T>> class vector;

Here, the Allocator template parameter (which itself is also a class template!) injects the memory allocation and deallocation policy into the parent class std::vector. If the user does not supply an allocator, the default std::allocator<T> is used.

As is typical in template-based polymporphism, the interface requirements on policy classes are implicit and semantic (based on valid expressions) rather than explicit and syntactic (based on the definition of virtual member functions).

Note that the more recent unordered associative containers, have more than one policy. In addition to the usual Allocator template parameter, they also take a Hash policy that defaults to std::hash<Key> function object. This allows users of unordered containers to configure them along multiple orthogonal dimensions (memory allocation and hashing).

Traits

Traits are class templates to extract properties from a generic type. There are two kind of traits: single-valued traits and multiple-valued traits. Examples of single-valued traits are the ones from the header <type_traits>

template< class T >
struct is_integral
{
static const bool value /* = true if T is integral, false otherwise */;
typedef std::integral_constant<bool, value> type;
};

Single-valued traits are often used in template-metaprogramming and SFINAE tricks to overload a function template based on a type condition.

Examples of multi-valued traits are the iterator_traits and allocator_traits from the headers <iterator> and <memory>, respectively. Since traits are class templates, they can be specialized. Below an example of the specialization of iterator_traits for T*

template<T>
struct iterator_traits<T*>
{
using difference_type = std::ptrdiff_t;
using value_type = T;
using pointer = T*;
using reference = T&;
using iterator_category = std::random_access_iterator_tag;
};

The expression std::iterator_traits<T>::value_type makes it possible to make generic code for full-fledged iterator classes usable even for raw pointers (since raw pointers don't have a member value_type).

Interaction between policies and traits

When writing your own generic libraries, it is important to think about ways users can specialize your own class templates. One has to be careful, however, not to let users fall victim to the One Definition Rule by using specializations of traits to inject rather than to extract behavior. To paraphrase this old post by Andrei Alexandrescu

The fundamental problem is that code that doesn't see the specialized
version of a trait will still compile, is likely to link, and
sometimes might even run. This is because in the absence of the
explicit specialization, the non-specialized template kicks in, likely
implementing a generic behavior that works for your special case as
well. Consequently, if not all the code in an application sees the
same definition of a trait, the ODR is violated.

The C++11 std::allocator_traits avoids these pitfalls by enforcing that all STL containers can only extract properties from their Allocator policies through std::allocator_traits<Allocator>. If users choose not to or forget to supply some of the required policy members, the traits class can step in and supply default values for those missing members. Because allocator_traits itself cannot be specialized, users always have to pass a fully defined allocator policy in order to customize their containers memory allocation, and no silent ODR violations can occur.

Note that as a library-writer, one can still specialize traits class templates (as the STL does in iterator_traits<T*>), but it is good practice to pass all user-defined specializations through policy classes into multi-valued traits that can extract the specialized behavior (as the STL does in allocator_traits<A>).

UPDATE: The ODR problems of user-defined specializations of traits classes happen mainly when traits are used as global class templates and you cannot guarantee that all future users will see all other user-defined specializations. Policies are local template parameters and contain all the relevant definitions, allowing them to be user-defined without interference in other code. Local template parameters that only contain type and constants -but no behaviorally functions- might still be called "traits" but they would not be visible to other code like the std::iterator_traits and std::allocator_traits.

Using traits over classes, why?

You can do many things with traits. I used it in my framework for example as Singleton and Getter/Setter.

trait Singleton
{
protected static $_instance;

protected function __construct(){}

public static function getInstance()
{
if (null === self::$_instance) {
self::$_instance = new static();
}
return self::$_instance;
}
}

Another interesting use is for Aspect Oriented Programming.
The answer would be to long to explain. Have a look here and here.

Question 2: If the traits have the same method than there will be a fatal error.
You have to use the insteadof operator for conflict resolution.
Look here

What are traits in C++, especially in boost

I'll explain how I see a traits class with a simple sample.

A definition could be: a trait allows extending T in a non-intrusive way.

A sample

Imagine you want to provide a geometric library, containing the 2D point concept (X, Y coordinates).
You provide a

/*
* @tparam T the coordinate type
*/
template <typename T>
class Point2D
{
T _x;
T _Y;
public:
/* some ctors */

/*
* Multiply this point by the given factor.
* @post this._x = factor * this._x
* this._y = factor * this._y
* @return reference to *this.
*/
Point2D<T> operator*=(double factor);
};

You choose to template the Point2D class so an user of your lib can choose the appropriate type (double if precision is needed, int if he works with pixels, ...). In Qt for instance, they impose int as coordinate type, which can be blocker for your project.
The type T needs to provide some information about the coordinate type concept: in you Point2D class, you will need to do with T:

  • Get the scalar type T is multipliable with (in my operator*=, I wrote double but it can be too intrusive)
  • Get a string representation of T
  • Compare 2 T (to implement you operator==)...

If you write your own Coordinate class, you can provide all the stuff. But if the user of your library wants to use int as T, he can't extend int.

Here arrives the traits: your Point2D will use the traits class CoordTypeTraits. It's purpose is to "extend" T type to provide everything you need from T as a coordinate concept (funtion, typedef...)

Sample:

typename <typedef T>
struct CoordTypeTraits
{
/*
* Define the scalar type T is multipiable with
*/
// typedef ... ScalarType;

/*
* Check the equality between 2 T, with a given precision
*/
// static bool IsCloseTo(const T& coord1, const T& coord2);

/*
* Return a string representation of T.
*/
// static string ToString(const T& coord);
}

Now you can access the information you need about T in your code, thanks to the traits class CoordTypeTraits:

/*
* @tparam T the coordinate type
*/
template <typename T>
class Point2D
{
T _x;
T _Y;
public:
/* some ctors */

/*
* @post this._x = factor * this._x
* this._y = factor * this._y
* @return reference to *this.
*/
Point2D<T> operator*=(CoordTypeTraits<T> factor);

const bool operator==(const T& rhs)
{
return CoordTypeTraits<T>.IsCloseTo(*this, rhs);
}

string ToString() const
{
return CoordTypeTraits<T>.ToString();
}
};

A user of you lib will use your Point2D with the type suitable for him, and he must provide (by specializing CoordTypeTraits for its type) a traits to "add" the data of the coordinate concept to T.

For instance, with double:

typedef Point2D<double> Point_double;

// specialization of CoordTypeTraits for double coordinate type
template<>
struct CoordTypeTraits<double>
{
typedef double ScalarType;

static bool IsCloseTo(const T& coord1, const T& coord2)
{
return (coord1 - coord2 < GetPrecision()) && (coord2 - coord1 < GetPrecision());
}

private:
static const double GetPrecision()
{
return 0.001; // because double is micrometers by convention, and I need nanometer precision
}

static string ToString(const double& coord)
{
return boost::lexical_cast<string>(coord);
}
}

For instance with int:

typedef Point2D<int> Point_int;

// specialization of CoordTypeTraits for double coordinate type
template<>
struct CoordTypeTraits<int>
{
typedef int ScalarType;

static bool IsCloseTo(const T& coord1, const T& coord2)
{
return coord1 == coord2;
}

static string ToString(const int& coord)
{
return boost::lexical_cast<string>(coord);
}
}

Conclusion and remarks

Your lib provide a Point2D class, so an user can use all the features you provide (compare, translate, rotate, ...).
He can do that with any coordinate type he wants, if he provides a traits to handle its type as a coordinate.
Usually, the libraries provide some common traits (here we would provide the Point_double and the Point_int).

Remark: I didn't try to compile, the code is just to illustrate.

Traits and classes: worried about length of class

The actual impact on parsing performance should be negligible. However, purely from a design standpoint, you should split this up into multiple classes and use composition, or the Composite Pattern:

he composite pattern describes that a group of objects is to be treated in the same way as a single instance of an object. The intent of a composite is to "compose" objects into tree structures to represent part-whole hierarchies. Implementing the composite pattern lets clients treat individual objects and compositions uniformly.

So, instead of traits, things like the "profile" should be objects of a class called MemberProfile, instantiated with information for this particular member. Inside Member, you could access something from the profile via $this->profile->getName(); or $this->profile->name;, for example.

Here's a quick example:

<?php

require_once 'MemberProfile.php';
require_once 'MemberAccount.php';

class MemberController extends LoggedUserController
{
public $profile;
public $account;

public function __construct()
{
$memberId = $_GET['memberId'];

$this->profile = new MemberProfile($memberId);
$this->account = new MemberAccount($memberId);
}

public function display()
{
$accountBalance = $this->account->getBalance();
$fullName = $this->profile->getFullName();

// ...
}
}

Inheritance and traits

  1. This is indeed impossible. You cannot inherit from two different classes at once, whether via traits or otherwise. This is unfortunate, but true. There is no very good reason for that AFAICT, it would be somewhat harder to implement, but not impossible, at least for scala (not "native java") classes. So apparently, it was just decided against at some point, seemingly without a good reason.

  2. Traits that extend classes aren't really "abnormal". It's more like the way scala compiler handles them is. Basically, anything, that is or extends a class has to appear in the extends clause, and not in with. Why? Well, why can you not compare Strings in java with ==? because ...

Why does Scala have classes when it already has traits?

There are several differences between traits and classes:

  • a trait can not take constructor parameters. This limitation might be lifted at some point, but it's a hard problem. A trait may be inherited multiple times in a hierarchy, and each instantiation may give different values for the constructor parameters

  • a trait is compiled to a Java interface and an implementation class (carrying the concrete methods). This means it's a bit slower, because all calls go through interfaces, and if they're concrete, they are forwarded to their implementation

  • a trait with concrete members can't be nicely inherited in Java (it could, but it would look like an interface, therefore concrete members would still need to be implemented in Java).

I don't think the distinction between classes and traits will go away, mostly because of the last two items. But they may become easier to use if the first point is solved. Regarding instantiation without the {}, that's a convenience that could be added, but I personally wouldn't like it: each instantiation creates a new class (an anonymous one), and there should be an indication to the programmer that that's the case.



Related Topics



Leave a reply



Submit