A use case for perfect forwarding

The Standard Library classes (such as std::string and std::vector) are not usually regarded as being able to be derived from. They are little black boxes whose functionality is clearly specified in the C++ Standard and which should not be redefined. However, let us put this to one side for a moment and consider that it should be possible, if not always desirable.

Say we want to add two new member functions to std::string, being utf8at() (which takes a index and returns a char32_t), and an overload to append() (which takes a char32_t). There are a number of ways of doing this:

  1. Composition: Our new class (String) has a member of type std::string, which is modified by String’s member functions. This likely has a performance overhead, and involves a lot of effort to create the class.
  2. Private Inheritance: Our new String is derived from std::string, but using private, not public, inheritance. None of the member functions of std::string are accessible from outside String, so forwarding functions (again) have to be written. This is almost as much work as (1).
  3. Non-member functions: We could forget about writing a whole new class and simply implement the two functions as free functions. However this is ugly and not best suited to Modern C++.
  4. Public Inheritance: All of std::string‘s member functions magically become available to clients of String. The only thing to remember is to not use a base class pointer under any circumstances (that is, reference a String* from a std::string* pointer, which would cause memory leaks upon delete). Let’s look at this option in more detail:

The following code compiles without any issues:

#include <string>
#include <iostream>

class String : public std::string {
};

int main() {
    String s;
    s.append("hello");
    std::cout << s << std::endl;
}

“Great!” you think, and get on with writing a program which uses String. However there are a couple of issues to solve. Firstly code like String s{ 8, '*' }; fails to compile with an error such as: “constructor overload resolution was ambiguous”. Secondly, the return type of member functions such as append() and substr() is still std::string, not String.

To solve the first of these issues using Modern C++, we can apply perfect forwarding to a generic constructor, which avoids us having to write out all of the possibilities manually. This can be achieved as shown here:

class String : public std::string {
public:
    template<typename... Args>
    explicit String(Args&&... args)
        : std::string(std::forward<Args>(args)...) {}
};

With perfect forwarding, String’s clients can enjoy access to all of std::string‘s constructors. Note the use of explicit, which ensured that any (possibly undesirable) automatic type conversions cannot take place; code which fails to compile is better than silently incorrect code. Also, note that perfect forwarding is guaranteed to not modify the parameters and should generate as efficient code as using the target function directly.

The second issue can be solved in almost the same way. The only problem with substr() is that its return type is std::string, not String; this is corrected using a safe static_cast<String>. The parameters do not need to be copied as we can use perfect forwarding again, as shown here:

class String : public std::string {
public:
// ...
    template<typename... Args>
    String substr(Args&&... args) {
        return static_cast<String>(std::string::substr(std::forward<Args>(args)...));
    }
};

This code is simply boilerplate which can be applied to other member functions of std::string which return *this (a macro could also be used to accomplish this).

So there you have it, perfect forwarding to base class constructors and member functions, without much effort, with no performance penalty and with full type safety. The only pain is the error messages which can be associated with such variadic-template-using functions.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s