views:

429

answers:

1

Some smart pointer templates, such as boost::shared_ptr, may be instantiated with void to hold an arbitrary object:

http://www.boost.org/doc/libs/1_39_0/libs/smart_ptr/sp_techniques.html#pvoid

Below is a minimal scoped_ptr implementation. When instantiated with void, the compiler complains about an illegal "reference to void" being formed in the dereference operator. It seems the "substitution failure is not an error" (SFINAE) rule does not cover this situation.

How then is it possible to implement a scoped_ptr? In particular, is there an alternative to writing a template specialization? This would cause large code reduplication with a realistic smart pointer implementation.

#include <cstdlib>

template<typename T>
void destroy(T* ptr)
{
    delete ptr;
}

class scoped_ptr_impl_base
{
public:
    virtual ~scoped_ptr_impl_base() { }
};

template<typename T, typename F>
class scoped_ptr_impl : public scoped_ptr_impl_base
{
public:
    scoped_ptr_impl(T* ptr, F dtor)
        : m_ptr(ptr), m_dtor(dtor)
    {
    }

    virtual ~scoped_ptr_impl()
    {
        m_dtor(m_ptr);
    }

private:
    T* m_ptr;
    F m_dtor;
};

template<typename T>
class scoped_ptr
{
public:
    explicit scoped_ptr(T* ptr = 0)
        : m_ptr(ptr),
          m_impl(new scoped_ptr_impl<T, void (*)(T*)>(&destroy<T>))
    {
    }

    template<typename F>
    scoped_ptr(T* ptr, F dtor)
        : m_ptr(ptr),
          m_impl(new scoped_ptr_impl<T, F>(ptr, dtor))
    {
    }

    ~scoped_ptr()
    {
        delete m_impl;
    }

    T& operator*()
    {
        return *m_ptr;
    }

    T* operator->()
    {
        return m_ptr;
    }

private:
    T* m_ptr;
    scoped_ptr_impl_base* m_impl;

    scoped_ptr(const scoped_ptr&);
    scoped_ptr& operator=(const scoped_ptr&);
};

int main()
{
    scoped_ptr<void> p(std::malloc(1), std::free);
    // scoped_ptr.cpp: In instantiation of `scoped_ptr<void>':
    // scoped_ptr.cpp:76:   instantiated from here
    // scoped_ptr.cpp:56: error: forming reference to void
    // (g++ 4.3.3)

    return 0;
}
+7  A: 

You could use a type trait for the reference type:

template<typename T>
struct type_trait
{
    typedef T& reference;
};

template<>
struct type_trait<void>
{
    typedef void reference;
};

then in your scoped_ptr_impl :

typename type_trait<T>::reference operator*()
{
    return *m_ptr;
}

Not sure if void is the right type in the specialisation though . What type do you want it to return?

jon hanson
+1. Tho i liked `void` better. It's what you would get if you could dereference a void pointer. Anyway, it's probably not important, because as soon as he tries to call operator*, he will get an error about dereferencing void*.
Johannes Schaub - litb
yea, void is definitely better than void *, since it'll cause an error if you try to dereference a `shared_ptr<void>` (which should be considered an error).
Evan Teran
Agreed, changed it back. Always go with your first instinct...
jon hanson
Thanks! This solution compiles fine here (g++ 4.3.3). If the void smart pointer is dereferenced, a good error message is produced ("'void*' is not a pointer-to-object type").The variant using plain 'void' as the reference type seems more intuitive at first, but produces an additional error message ("return-statement with a value, in function returning 'void'"). I suppose this why you preferred to use 'void*'.
cj
cj, you can fix that other message by doing `return (typename type_trait<T>::reference) *m_ptr;` instead of a plain return.
Johannes Schaub - litb
+1, answers the question well. I think, just for good measure, you will need specializations for `const void`, `volatile void` and `const volatile void` as well.
Evan Teran
cj
Sure but then you are back to the old problem with `void*` again. Stuff like `sizeof(*ptr)` will give you sizeof of a `char` or `void*`, not an error about taking the sizeof of a `void`. I think my recommendation needs a little explanation though: a return statement there has to either return nothing, or a void expression. For example, the following is valid `void f() { return (void) 1; }`. The explicit cast exactly accounts for this.
Johannes Schaub - litb
@litb Hm you're completely right about the problem with sizeof(*ptr). Really has to be 'void' in the type traits.
cj