Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

typedef vs public inheritance in c++ meta-programming

Disclaimer: the question is completely different from Inheritance instead of typedef and I could not find any similar question so far

I like to play with c++ template meta-programming (at home mostly, I sometimes introduce it lightly at work but I don't want to the program to become only readable to anyone who did not bother learning about it), however I have been quite put out by the compiler errors whenever something goes wrong.

The problem is that of course c++ template meta-programming is based on template, and therefore anytime you get a compiler error within a deeply nested template structure, you've got to dig your way in a 10-lines error message. I have even taken the habit of copy/pasting the message in a text-editor and then indent the message to get some structure until I get an idea of what is actually happening, which adds some work to tracking the error itself.

As far as I know, the problem is mostly due to the compiler and how it output typedefs (there are other problems like the depth of nesting, but then it's not really the compiler fault). Cool features like variadic templates or type deduction (auto) are announced for the upcoming C++0x but I would really like to have better error messages to boot. It can prove painful to use template meta-programming, and I do wonder what this will become when more people actually get into them.

I have replaced some of the typedefs in my code, and use inheritance instead.

typedef partition<AnyType> MyArg;

struct MyArg2: partition<AnyType> {};

That's not much more characters to type, and this is not less readable in my opinion. In fact it might even be more readable, since it guarantees that the new type declared appears close to the left margin, instead of being at an undetermined offset to the right.

This however involves another problem. In order to make sure that I didn't do anything stupid, I often wrote my templates functions / classes like so:

template <class T> T& get(partition<T>&);

This way I was sure that it can only be invoked for a suitable object.

Especially when overloading operators such as operator+ you need some way to narrow down the scope of your operators, or run the risk of it been invoked for int's for example.

However, if this works with a typedef'ed type, since it is only an alias. It sure does not work with inheritance...

For functions, one can simply use the CRTP

template <class Derived, class T> partition;

template <class Derived, class T> T& get(partition<Derived,T>&);

This allows to know the 'real' type that was used to invoke the method before the compiler used the public inheritance. One should note that this decrease the chances this particular function has to be invoked since the compiler has to perform a transformation, but I never noticed any problem so far.

Another solution to this problem is adding a 'tag' property to my types, to distinguish them from one another, and then count on SFINAE.

struct partition_tag {};

template <class T> struct partition { typedef partition_tag tag; ... };

template <class T>
typename boost::enable_if<
  boost::same_type<
    typename T::tag,
    partition_tag
  >,
  T&
>::type
get(T&)
{
  ...
}

It requires some more typing though, especially if one declares and defines the function / method at different places (and if I don't bother my interface is pretty soon jumbled). However when it comes to classes, since no transformation of types is performed, it does get more complicated:

template <class T>
class MyClass { /* stuff */ };

// Use of boost::enable_if

template <class T, class Enable = void>
class MyClass { /* empty */ };

template <class T>
class MyClass <
  T,
  boost::enable_if<
    boost::same_type<
      typename T::tag,
      partition_tag
    >
  >
>
{
  /* useful stuff here */
};

// OR use of the static assert

template <class T>
class MyClass
{
  BOOST_STATIC_ASSERT((/*this comparison of tags...*/));
};

I tend to use more the 'static assert' that the 'enable_if', I think it is much more readable when I come back after some time.

Well, basically I have not made my mind yet and I am still experimenting between the different technics exposed here.

Do you use typedefs or inheritance ? How do you restrict the scope of your methods / functions or otherwise control the type of the arguments provided to them (and for classes) ?

And of course, I'd like more that personal preferences if possible. If there is a sound reason to use a particular technic, I'd rather know about it!

EDIT:

I was browsing stackoverflow and just found this perl from Boost.MPL I had completely forgotten:

BOOST_MPL_ASSERT_MSG

The idea is that you give the macro 3 arguments:

  • The condition to check
  • a message (C++ identifier) that should be used for display in the error message
  • the list of types involved (as a tuple)

It may help considerably in both code self documentation and better error output.

like image 576
Matthieu M. Avatar asked Oct 04 '09 11:10

Matthieu M.


1 Answers

What you are trying to do is to explicitly check whether types passed as template arguments provide the concepts necessary. Short of the concept feature, which was thrown out of C++0X (and thus being one of the main culprits for it becoming C++1X) it's certainly hard to do proper concept checking. Since the 90ies there have been several attempts to create concept-checking libraries without language support, but, basically, all these have achieved is to show that, in order to do it right, concepts need to become a feature of the core language, rather than a library-only feature.

I don't find your ideas of deriving instead of typedef and using enable_if very appealing. As you have said yourself, it often obscures the actual code only for the sake of better compiler error messages.

I find the static assert a lot better. It doesn't require changing the actual code, we all are used to having assertion checks in algorithms and learned to mentally skip over them if we want to understand the actual algorithms, it might produce better error messages, and it will carry over to C++1X better, which is going to have a static_assert (completely with class designer-provided error messages) built in into the language. (I suspect BOOST_STATIC_ASSERT to simply use the built-in static_assert if that's available.)

like image 105
sbi Avatar answered Oct 11 '22 04:10

sbi