c++ - Optionally safety-checked cast on possibly incomplete type -
pursuant simple, intrusively reference-counted object system, have template<typename t> class handle, meant instantiated subclass of countedbase. handle<t> holds pointer t, , destructor calls decref (defined in countedbase) on pointer.
normally, cause problems when trying limit header dependencies using forward declarations:
#include "handle.h" class foo; // forward declaration struct mystruct { handle<foo> foo; // okay, but... }; void bar() { mystruct ms; } // ...there's error here, implicit ~mystruct calls // handle<foo>::~handle(), wants foo complete // type can call foo::decref(). solve this, have // #include definition of foo. as solution, rewrote handle<t>::~handle() follows:
template<typename t> handle<t>::~handle() { reinterpret_cast<countedbase*>(m_ptr)->decref(); } note i'm using reinterpret_cast here instead of static_cast, since reinterpret_cast doesn't require definition of t complete. of course, won't perform pointer adjustment me... long i'm careful layouts (t must have countedbase leftmost ancestor, must not inherit virtually, , on couple of unusual platforms, vtable magic necessary), it's safe.
what really nice, though, if layer of static_cast safety possible. in practice, definition of t usually complete @ point handle::~handle instantiated, making perfect moment double-check t inherits countedbase. if it's incomplete, there's not can do... if it's complete, sanity check good.
which brings us, finally, question: is there way compile-time check t inherits countedbase not cause (spurious) error when t not complete?
[usual disclaimer: i'm aware there potentially unsafe and/or ub aspects use of incomplete types in way. nevertheless, after great deal of cross-platform testing , profiling, i've determined practical approach given unique aspects of use case. i'm interested in compile-time checking question, not general code review.]
using sfinae on sizeof check whether type complete :
struct countedbase { void decref() {} }; struct incomplete; struct complete : countedbase {}; template <std::size_t> struct size_tag; template <class t> void decref(t *ptr, size_tag<sizeof(t)>*) { std::cout << "static\n"; static_cast<countedbase*>(ptr)->decref(); } template <class t> void decref(t *ptr, ...) { std::cout << "reinterpret\n"; reinterpret_cast<countedbase*>(ptr)->decref(); } template <class t> struct handle { ~handle() { decref(m_ptr, nullptr); } t *m_ptr = nullptr; }; int main() { handle<incomplete> h1; handle<complete> h2; } output (note order of destruction reversed) :
static reinterpret trying complete type not derive countedbase yields :
main.cpp:16:5: error: static_cast 'oops *' 'countedbase *' not allowed that being said, think more elegant (and more explicit) approach introduce class template incomplete<t>, such handle<incomplete<foo>> compiles reinterpret_cast, , else tries static_cast.
Comments
Post a Comment