2

I'm studying Boost Library and find it uses traits concept a lot, like iterator_traits, graph_traits. What does traits mean? Could you give me a simple but concise example that tell us why we need traits. To my current knowledge, "traits" seems mean that it contains all the types we may need, such that we won't get something wrong with the types.
The following is the graph_traits template in boost:

template <typename Graph>
  struct graph_traits {
    typedef typename Graph::vertex_descriptor      vertex_descriptor;
    typedef typename Graph::edge_descriptor        edge_descriptor;
    typedef typename Graph::adjacency_iterator     adjacency_iterator;
    typedef typename Graph::out_edge_iterator      out_edge_iterator;
    typedef typename Graph::in_edge_iterator       in_edge_iterator;
    typedef typename Graph::vertex_iterator        vertex_iterator;
    typedef typename Graph::edge_iterator          edge_iterator;

    typedef typename Graph::directed_category      directed_category;
    typedef typename Graph::edge_parallel_category edge_parallel_category;
    typedef typename Graph::traversal_category     traversal_category;

    typedef typename Graph::vertices_size_type     vertices_size_type;
    typedef typename Graph::edges_size_type        edges_size_type;
    typedef typename Graph::degree_size_type       degree_size_type;
  };
Cœur
  • 37,241
  • 25
  • 195
  • 267
Emerson Xu
  • 428
  • 6
  • 13

2 Answers2

6

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.

Nico
  • 309
  • 1
  • 13
0

Traits could be replaced (as a word) with "characteristics".

They are a C++ technique that uses template specialization to associate different types, operations and constants to a type (effectively creating metadata).

utnapistim
  • 26,809
  • 3
  • 46
  • 82