Subtle differences in C++

Let’s start this post with a small C++ quiz. Consider the following class:

class Eater
{
public:
  template <size_t N>
  void Feed(const char (&str)[N]);

  void Feed(const char* str);
};

In case you are not familiar with the syntax (and admittedly it’s rather cryptic), the first member function takes a reference to an array of const chars, where the array can be of any size (the compiler will automatically deduce N if possible).

Now consider the following code using our Eater class:

// an actual array of characters living on the stack
const char s1[] = "test";
// a pointer on the stack which points to characters living in a different segment, probably .rodata or .sdata
const char* s2 = "test";

Eater eater;
eater.Feed(s1);
eater.Feed(s2);
// of which type is "test" in this case?
eater.Feed("test");

The comments are already an indicator of the subtle differences in declaring something we often refer to as a “string” (eventhough “string” is not really the correct C++ term here).

So the question is: which member functions will get called? Before you go and try it out, I’ll tell you. The code calls Eater::Feed(const char* str) three times – I guess that comes as a bit of a surprise!

Looking at C++’s overload resolution rules (which can be found somewhere in the C++ standard), we can see why: the compiler prefers the non-templated function to the templated one if it can convert the argument to a const char* (functions overloads come first, templates second, and the ellipsis comes last). Turns out that converting arguments to a ‘const char*’ is possible in all three cases, hence no call to the template function. What we really want is the compiler to call the template function for “constant strings” such as s1 and “test” in our example, so we can do some fancy stuff with it. There’s no reason why the compiler can’t call it – it knows the type and size of the array at compile-time.

With a small helper structure, we can have our cake and eat it, by changing our Eater class to the following:

class Eater
{
public:
  struct ConstCharWrapper
  {
    // non-explicit constructor on purpose!
    ConstCharWrapper(const char* str) : m_str(str) {}
    const char* m_str;
  };

  template <size_t N>
  void Feed(const char (&str)[N]);

  void Feed(ConstCharWrapper str);
};

Eater::Feed() now no longer takes a ‘const char*’ as parameter, but rather a ‘ConstCharWrapper’. This forces the compiler to choose the template function for ‘s1’ and “test” in our example, because otherwise the argument to Eater::Feed() would have to undergo an implicit conversion, making the template function a better match in this case (again, it all has to do with C++’s overload resolution rules).

This leaves us with a class that can be fed constant character arrays with which we can do fancy stuff if we want to, and which still works for non-constant strings (such as std::string.c_str()) as well. Next time, we’ll put it to good use by interning/hashing strings almost at compile-time – you would be surprised at how good C++ compilers are when it comes to templates, template specializations, and the likes.

6 thoughts on “Subtle differences in C++

  1. Pingback: Hashed strings | Molecular Musings

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.