Showing posts with label programming. Show all posts
Showing posts with label programming. Show all posts

Wednesday, September 26, 2007

Working around cv-qualifiers when specializing template classes

A great principle states that "libraries should be open to extension and closed to modification". This article tries to address an inconvenience when writing generic libraries that are extensible by the means of template specialization.

Imagine a generic class that implements a hashed set. It's signature would be something like:


template <class Key, class Hash = hash<Key>>
class hash_set;


Unless the user doesn't specify a hashing function explicitly the library uses a specialization of its 'hash' class. One such class would look something like:


template <class T>
struct hash
{
  size_t operator () (const T& x) const {
    return ???;
  }
};


Suddenly a problem occurs - we don't know how to hash every possible type. We'll just have to go with specializations and leave the unspecialized class undefined so that a compile-time error is raised if the hash doesn't know how to do so.


template <class T> struct hash;


Now, a concise library writer would provide specializations for the hash class for some basic types, so that the hash_set is immediately useful for simple cases.


template <> struct hash<char> {...}
template <> struct hash<short> {...}
template <> struct hash<int> {...}
template <> struct hash<char*> {...}
template <> struct hash<std::string> {...}
template <> struct hash<etc.> {...}


Now, our user can specialize the hash class for custom types and thus extend the library's reach.

As our users happily trek along different instantiations of the hash_set template suddenly a calamity strikes! Our user tries to instantiate a hash_set<const int>. Of course the compiler complains that no such specialization of hash<> exists. The user is discontent, the library vendor is in a stupor. A common scenario, really.

The tedious solution in such cases is to write a specialization for both const and non-const types. Yuck.

Luckily for us Boost provides a nice feature in its TypeTraits library - remove_const. Using it we can rewrite our hash_set as follows:


template <class Key, class Hash = hash<typename boost::remove_const<Key>::type>>
class hash_set;


Excellent. However, if our library vendor ever wants to write another class that uses a hash, for example a hash_map, they have to remember to add the remove_const bit. Each time. And what if our const remover happens to change?

Being smart about code responsibilities we come up with the following solution - the const remover will become a part of the hashing library, instead of the container library:


template <class T>
struct select_hash {
  typedef hash<typename boost::remove_const<T>::type> type;
};


or even better:


template <class T>
struct select_hash
  : hash<typename boost::remove_const<T>::type>
{};


Thus usage becomes:
template <class Key, class Hash = select_hash<Key>>
class hash_set;

Now we're set for clear sailing. Yet there is a way to be even cleverer, thus removing the need of select_hash and relying solely on hash. Observe as we change our unspecialized template:


template <class T>
struct hash
  : hash<typename boost::remove_const<T>::type>
{};


Now each instantiation of hash<> either stumbles on a perfect specialization (like hash<int>), or the generic one, that first tries to strip the const qualifier. Thus, a hash<const int> inherits hash<int> and it's a perfect circle. When used with a type that hash has no specialization for, the compiler raises an error that 'a class cannot inherit from itself'. We're done here.

Note: to remove the volatile qualifier as well use boost::remove_cv<>. Depending on your situation this may or may not be a useful move.

Monday, September 17, 2007

Omit Needless Words (in C++)

I came across a small C++ language feature which I didn't know about. It is a very simple syntactic sugar for omitting needless words. Many of you may already know it but to those who don't I say: read this short blog entry

Omit Needless Words (in C++)


After I shared this with my colleagues I asked them "So can you think of any other situation when the ThisClass typedef in a class is obligatory,... apart from generic programming?" They laughed and said that I had just asked a question "Can something be useful for any other thing than the only one it is useful for?" I was left with my doubts. Can you, readers, prove that wrong? :)

Saturday, September 15, 2007

Named Constructors

Here is a little trick, that all you old-school C++ programmers might not know. Obviously it's called "Named Constructors".

So, suppose you need to make a 3x3 matrix class for your game-math library. You need it to make affine transformations so you have to make it represent rotation and scaling. So here is some code, that you'll probably write:


class matrix3x3
{
public:
   //scaling
   matrix3x3(float xscale, float yscale, float zscale);
   matrix3x3(float uniform_scale);

   //rotation
   matrix3x3(const vec3& axis, float angle);
   matrix3x3(float yaw, float pitch, float yaw);

   //other stuff
   //...
};


Great. But wait a minute! We have two constructors that take three floats each. Damn!

Obviously constructs like


matrix3x3 m(3, 1, 2); //wtf is this? Scaling?


...and the pure impossibility of having two constructors with the same signature make this a bad choice.

So, what can you do?
There is an old-school pattern to write external constructors. Like that:


void matrix_scaling(matrix& out, float xscale ...
void matrix_rotation_yaw_pitch_roll(matrix& out ...


That just doesn't look good. Luckily there is a thing called Return Value Optimization, and specifically Named Return Value Optimization or NRVO (try reading that with your mouth full). Basically NRVO is something that allows you to return heavy objects from functions without worying that they'll be copied, which is slow. And game developers don't like slow. They don't even know what slow means. It's either acceptable or not acceptable. Yeah!... Anywho, this is a link to a blog that explains just how cool NRVO is.

And how did we use it to create our named constructors? That's how:


class matrix3x3
{
public:
   static matrix3x3 scaling(float x, float y, float z)
   {
      matrix3x3 ret;
      //fill ret with appropriate stuff...
      return ret;
   }
   //you get the point...
   //...
   //you really get the point. there is no need for more examples.
};


And just look at how neat the code looks with the named constructors.


matrix3x3 m1 = matrix3x3::scaling(1, 2, 3);
matrix3x3 m2 = matrix3x3::rotation_yaw_pitch_roll(1, 2, 3);


Self documenting code at its finest. And no efficiency loss whatsoever.

I should mention that this is a relatively new standart feature and if you use retro compilers (less than vc8 or less than gcc4) you most probably will suffer from efficiency loss. So hurry and get something contemporary.

If you don't want to bother and read the link I posted above and just want to see whether your compiler has NRVO (I keep reading it "nervo" in my head... that's not right), just test this:


struct nervo //hehe
{
   nervo() : n(23) { cout << "hello world, baby" << endl; }
   nervo(const nervo& s) : n(s.n) { cout << "cloned!!!111one" << endl; }
   ~nervo() { cout << "lights getting dim..." << endl; }
   int n;
};

nervo test()
{
   nervo n;
   return n;
}

...

nervo x = test();

If there's no text like the one from the copy-constructor, you're good to go!

Friday, September 14, 2007

Our Predicated Construction Library

Introducing our predicated construction library that eases conditional usage of sentry (RAII) classes.

Motivating example:

Suppose you have a sentry class that enables wireframe rendering mode in some Direct3D context and upon destruction reverts solid fill.


struct WireframeSentry
{
   WireframeSentry(IDirect3DDevice9* device)
   : m_Device(device)
   {
      m_Device->SetRenderState(D3DRS_FILLMODE, D3DFILL_WIREFRAME);
   }

   ~WireframeSentry()
   {
      m_Device->SetRenderState(D3DRS_FILLMODE, D3DFILL_SOLID);
   }

private:
   IDirect3DDevice9* m_Device;
};


The question is - how can you enable wireframe rendering through the sentry if it's optional, as in you want to enable wireframe rendering based on some external predicate. Obviously you can't optionally skip the constructor of a locally instantiated object.

if (renderInWireframe)
   WireframeSentry(device);


We can do so if, for example we provide a second bool parameter in the constructor:

WireframeSentry(IDirect3DDevice9* device, bool reallyEnable=true)

Clearly this solution doesn't scale, and it doesn't attack the problem at hand - how to conditionally create objects *and* reap the fruit of the destructor-on-scope-exit that the compiler guarantees for local objects.

Solution:

Enter predicated construction. Our example will be superceded by:

bool renderInWireframe = ...;
BOOST_PREDICATED_ANONYMOUS_CONSTRUCTOR(renderInWireframe, WireframeSentry, (device));


If renderInWireframe is true then an unnamed object will be created and destroyed on scope exit. Otherwise nothing will happen (save for an 'if').

Link to the code @ Boost vault