I've stumbled upon this code (simplified version here):
template<typename T>
class SmartPtr;
template<typename T>
struct SmartPtr<const T>
{
const T& operator*() const { return *_ptr; }
const T* operator->() const { return _ptr; }
const T* _ptr;
// more member data and functions here
// ...
};
template<typename T>
struct SmartPtr : public SmartPtr<const T>
{
T& operator*() const { return *const_cast<T*>(_ptr); }
T* operator->() const { return const_cast<T*>(_ptr); }
};
It's the first time I see a template class derived like this. Can anyone help me understand what one can achieve using this? To me it seems the general version behaves the same as the specialization if instantiated with a const T
and the specialization is not necessary. Am I missing something?
The general behaviour of SmartPtr<T>
and SmartPtr<const T>
is indeed identical and no specialization would have been needed simply to handle const and non-const versions of T.
However, the answer to your question lies in that this construction allows for implicit conversion from SmartPtr<T>
to SmartPtr<const T>
.
For instance, a function defined for SmartPtr<const foo>
can be called with a SmartPtr<foo>
as well, and thus you don't need two overloads to handle both cases:
void HandleFoo(SmartPtr<foo> ptr);
void HandleConstFoo(SmartPtr<const foo> ptr);
void dotask()
{
SmartPtr<foo> fooptr = new foo ...;
...
HandleFoo(fooptr);
HandleConstFoo(fooptr); // implicit conversion
}
Simply defining a single class SmartPtr<T>
does not allow for implicit conversion from SmartPtr<T>
to SmartPtr<const T>
.
For implicit conversion between different types you would normally add extra member functions, but in this case for the same class from non-const to const you would run into problems.
A specialization with a const T
base class is one of the ways to avoid those problems.
To me it seems the general version behaves the same as the specialization if instantiated with a
const T
If instantiated with a const T
, the primary template version isn't used at all. The specialization will be used.
If instantiated with a non-const T
, then the primary simply reuses the code from the specialization, by tweaking the return types to make sense for a pointer to a non-const piece of data.
It's also a way to get "expected" behavior between the SmartPtr
specializations (to behave as standard raw pointers do with regard to qualification conversions). We can add converting constructors of course but not all cases are covered with this approach. Consider this
template<class U>
void frombulate(SmartPtr<const U>) {
}
Can we call frombulate
with a SmartPtr<int>
? In this implementation we can (because derived-to-base conversions are allowed in template argument deduction). But a converting constructor would not allow us that, since general conversions in template argument deduction may not be considered.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With