4
\$\begingroup\$

Context: I'm using the clone pattern for creating copies of polymorphic classes which may use virtual inheritance (I hope to get rid of the latter). So when calling foo.clone() I'm guaranteed to get the same type as foo even though the clone function might return only the base class (say ICloneable)

So I wrapped the clone call into a freestanding clone function which casts the pointer to the class passed and additionally puts it into a unique_ptr.

However the cast is a problem. dynamic_cast has too much overhead as I already know what type I'll get and static_cast doesn't work with base to derived casts on virtual bases.

So this is what I came up with:

template<typename T, typename U, typename = void>
struct is_static_castable : std::false_type
{};

template<typename T, typename U>
struct is_static_castable<T, U, decltype(void(static_cast<U>(std::declval<T>())))> : std::true_type
{};

template<typename To, typename From>
std::enable_if_t<is_static_castable<From*, To*>::value, To*>
safePtrCast(From* from)
{
    return static_cast<To*>(from);
}

template<typename To, typename From>
std::enable_if_t<!is_static_castable<From*, To*>::value, To*>
safePtrCast(From* from)
{
    return dynamic_cast<To*>(from);
}

template<class T>
auto clone(const T* obj)
{
    return std::unique_ptr<T>(safePtrCast<T>(obj->clone()));
}

Some notes:

  • C++14
  • void conversion in is_static_castable is required so MSVC 2015 does not fail for is_static_castable<int, int>, no idea why
  • safePtrCast can be enhanced with an assert(dynamic_cast... if wanted
  • return type enable_if used due to a bug in GCC 8: https://godbolt.org/z/6-C6CS

Any remarks on potential issues?
Possible cases I missed?
Improvements to make it more readable?

Otherwise feel free to use the code for own projects :)

The non-MSVC version of is_static_castable is slightly clearer:

template<typename T, typename U, typename = U>
struct is_static_castable : std::false_type
{};

template<typename T, typename U>
struct is_static_castable<T, U, decltype(static_cast<U>(std::declval<T>()))> : std::true_type
{};

Example for not using covariant return types:

struct ICloneable{ virtual ICloneable* clone() = 0; };
struct Middle: ICloneable{...};
struct Final: Middle{ Final* clone(); };

void foo(Middle* bar){
  auto barCloned = clone(bar); // Should be a unique_ptr<Middle>
}
\$\endgroup\$
  • \$\begingroup\$ Re "void conversion in is_static_castable is required so MSVC 2015 does not fail for is_static_castable<int, int>, no idea why": otherwise the type is not void, and typename = void does not select it. This is not restricted to MSVC 2015. \$\endgroup\$ – L. F. Apr 13 at 11:08
  • \$\begingroup\$ Oh right. I added a note how it would look like for all but MSVC (not using void) \$\endgroup\$ – Flamefire Apr 13 at 11:32
  • \$\begingroup\$ "even though the clone function might return only the base class"... can you show an example class hierarchy that requires this? The normal solution is to use covariant return types (this is actually the classic motivating example for the language feature): en.wikibooks.org/wiki/More_C%2B%2B_Idioms/… \$\endgroup\$ – user673679 Apr 13 at 12:21
  • 2
    \$\begingroup\$ I took me a while to find the use case again but now I added it: It occurs when you have a pointer to a class from the middle of the hierarchy. Only the final classes should implement the clone function (I might even static_assert this) to avoid mistakes. Obviously this is use-case dependent but for this question assume that there are "middle" classes which have other pure-virtual functions so a clone implementation there would not be useful. \$\endgroup\$ – Flamefire Apr 13 at 13:37

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.