Typedef in traits vs typedef in class
Asked Answered
A

2

9

I'm looking through the Eigen source code for educational purposes. I've noticed that for each concrete class template X in the hierarchy, there is an internal::traits<X> defined. A typical example can be found in Matrix.h:

namespace internal {
template<typename _Scalar, int _Rows, int _Cols, int _Options, int _MaxRows, int _MaxCols>
struct traits<Matrix<_Scalar, _Rows, _Cols, _Options, _MaxRows, _MaxCols> >
{
  typedef _Scalar Scalar;
  typedef Dense StorageKind;
  typedef DenseIndex Index;
  typedef MatrixXpr XprKind;
  enum {
    RowsAtCompileTime = _Rows,
    ColsAtCompileTime = _Cols,
    MaxRowsAtCompileTime = _MaxRows,
    MaxColsAtCompileTime = _MaxCols,
    Flags = compute_matrix_flags<_Scalar, _Rows, _Cols, _Options, _MaxRows, _MaxCols>::ret,
    CoeffReadCost = NumTraits<Scalar>::ReadCost,
    Options = _Options,
    InnerStrideAtCompileTime = 1,
    OuterStrideAtCompileTime = (Options&RowMajor) ? ColsAtCompileTime : RowsAtCompileTime
  };
};
}

Now I understand traits to be a way of extending existing classes that you do not want to modify with extra information pertaining to some piece of new code. For example, a user of class template Foo<class TAllocator> might want to make use of existing memory allocators FastAlloc and AlignedAlloc, but Foo needs to know how to interface with these two, and as such a FooTraits<AlignedAlloc>::allocate() and FooTraits<FastAlloc>::allocate() are defined by the user, which in turn are used by Foo.

In this case, however, I don't readily see the problem with just specifying Scalar in each derived class, i.e. have Matrix define Matrix::Scalar using a typedef in the class body. What is the advantage here of using a traits class? Is it just for the purposes of keeping the code clean, i.e. storing all relevant properties of each class in the traits class?

Edit as per Nicol Bolas's response: I understand that some of these typedefs might need to be kept "internal", i.e. should not be exposed to the user, which would explain the traits class. That seems to make sense, however some of these typedefs, such as Scalar, are available to the outside world, through a typedef in the base class of Matrix:

template<typename Derived> class MatrixBase
  : public DenseBase<Derived>
{
  public:

    typedef MatrixBase StorageBaseType;
    typedef typename internal::traits<Derived>::StorageKind StorageKind;
    typedef typename internal::traits<Derived>::Index Index;
    typedef typename internal::traits<Derived>::Scalar Scalar;
    typedef typename internal::packet_traits<Scalar>::type PacketScalar;
    typedef typename NumTraits<Scalar>::Real RealScalar;

This brings us back to the original question: why isn't Scalar just a typedef in Matrix itself? Is there any reason aside from stylistic choice?

Arabic answered 24/8, 2016 at 20:46 Comment(0)
C
8

I suspect that, since the traits class is internal, that this is the point of using a traits class. That is, to keep these things internal. That way, Matrix doesn't have a lot of oddball definitions and such, even in its private interface.

Consider the enumeration in your example. Those "enums" (aka: static constexpr variables before C++11) don't look like anything that a user should know about. It's an implementation detail, and therefore it should be hidden.


MatrixBase's problem is a CRTP issue.

See, Matrix would be defined like this:

class Matrix : public MatrixBase<Matrix>

This partial definition causes 2 things to happen:

  1. If Matrix has not already been declared as a class type, then it becomes a legal class who's name can be referenced and used.

  2. The template MatrixBase must be instantiated with the type Matrix. Right now.

The problem here is that "right now", Matrix is an incomplete class. The compiler has not yet entered the body of that definition, so the compiler doesn't know anything about its internals. But MatrixBase must be instantiated right now.

Therefore, MatrixBase cannot use any of the contents of the Derived class it is provided. If Matrix has some typedef in it, MatrixBase<Derived> cannot see it.

Now, member functions of MatrixBase<Derived> can look at definitions in Derived, because those are defined after the full class is defined. Even if those functions are defined within the scope of the class.

But you can't have properties of MatrixBase access properties of Derived. Hence the traits indirection. The traits class can use a specialization based on an incomplete type to expose defines to MatrixBase.

Confession answered 24/8, 2016 at 21:42 Comment(2)
I understand that reasoning for some of those definitions. However, the class from which Matrix inherits, MatrixBase, publicly defines some of these typedefs, such as Scalar, which brings us back to the start - why not define Scalar in Matrix to begin with?Arabic
Very clear - thank you very much for taking the time!Arabic
S
4

The main reason for this traits class is to avoid recursive dependencies in the CRTP. Without it, we would end up with something like:

template <typename T>
struct Matrix : Base<Matrix<T>> {
  typedef T Scalar;
};
template <typename Derived>
struct Base {
  typename Derived::Scalar foo();
};

that fails to compile in some circumstances. Basically, this class traits permits to fully declare Base<Matrix> without knowing the declaration of Matrix.

Stick answered 24/8, 2016 at 22:10 Comment(1)
Ah, I see, makes sense. Thanks!Arabic

© 2022 - 2024 — McMap. All rights reserved.