Wednesday, February 22, 2012

Generic constructors

C++ gives us two ways of enabling implicit type conversions - single-parameter constructors and overloaded conversion operators. Both of those, however, are limited in that they must be defined within the class that the conversion is occurring from or two. As C++ does not have the concept of partial classes this can be an undesirable limitation.

Generic constructors are a handy idiom which can be used to circumvent this limitation. A generic constructor in C++11 looks like this:

class Foo
{
public:
    Foo();

    Foo(Foo&&);

    template <typename T>
    Foo(T&& param)
    : Foo(make_foo(std::forward<T>(param))
    {
    }
};

First, a couple of requirements - either a copy or move constructor is required for this idiom to work. (This isn't a hard requirement, strictly speaking, but forwarding to the move constructor is the cleanest implementation.) A move constructor is preferable due to better performance. Second, some other sort of constructor is necessary so that it's possible to implement make_foo() functions. A default constructor works fine. Once we've defined this class, we can now define make_foo() functions for all of the implicit conversions
we'd like to take place. For example:

Foo make_foo(int i)
{
    Foo f;
    f.set_value(i);
    return std::move(f);
}

Foo make_foo(std::string s)
{
    Foo f;
    f.set_value(s);
    return std::move(f);
}

The key advantage here is that make_foo can be defined somewhere else in the code, possibly not even in the same library. If Foo is provided in one library, and Bar in another, we can define a make_foo(Bar) function which allows Foo to be implicitly constructed from Bar, even though neither class is visible to the other.

Although implicit conversions are something to be avoided in general, in particular cases this can be extremely useful. I use this pattern in polymorphic_collections to allow the various adapters (such as enumerator) to be extensible, so if you need to interface with a non-STL collections library such as Qt, it's as straightforward as writing an adapter implementation and make_enumerator function. 

No comments:

Post a Comment