1289. Generic casting requirements for smart pointers

Section: 23.2 [utility] Status: LEWG Submitter: Ion Gaztañaga Opened: 2009-12-14 Last modified: 2016-02-10

Priority: Not Prioritized

View all other issues in [utility].

View all issues with LEWG status.

Discussion:

In section 20.5.3.5 [allocator.requirements], Table 40 — Allocator requirements, the following expression is required for allocator pointers:

Table 40 — Allocator requirements
Expression Return type Assertion/note
pre-/post-condition
Default
static_cast<X::pointer>(w) X::pointer static_cast<X::pointer>(w) == p  

To achieve this expression, a smart pointer writer must introduce an explicit conversion operator from smart_ptr<void> to smart_ptr<T> so that static_cast<pointer>(void_ptr) is a valid expression. Unfortunately this explicit conversion weakens the safety of a smart pointer since the following expression (invalid for raw pointers) would become valid:

smart_ptr<void> smart_v = ...;
smart_ptr<T> smart_t(smart_v);

On the other hand, shared_ptr also defines its own casting functions in 23.11.3.9 [util.smartptr.shared.cast], and although it's unlikely that a programmer will use shared_ptr as allocator::pointer, having two different ways to do the same cast operation does not seem reasonable. A possible solution would be to replace static_cast<X::pointer>(w) expression with a user customizable (via ADL) static_pointer_cast<value_type>(w), and establish the xxx_pointer_cast functions introduced by shared_ptr as the recommended generic casting utilities of the standard.

Unfortunately, we've experienced problems in Boost when trying to establish xxx_pointer_cast as customization points for generic libraries (http://objectmix.com/c/40424-adl-lookup-explicit-template-parameters.html) because these casting functions are called with explicit template parameters and the standard says in 17.9.1 [temp.arg.explicit] p.8 "Explicit template argument specification":

8 ...But when a function template with explicit template arguments is used, the call does not have the correct syntactic form unless there is a function template with that name visible at the point of the call. If no such name is visible, the call is not syntactically well-formed and argument-dependent lookup does not apply.

So we can do this:

template<class BasePtr>
void generic_ptr_swap(BasePtr p)
{
  //ADL customization point
  swap(p, p);
  //...
}

but not the following:

template<class BasePtr>
void generic_ptr_algo(BasePtr p)
{
  typedef std::pointer_traits<BasePtr>::template
     rebind<Derived> DerivedPtr;
  DerivedPtr dp = static_pointer_cast<Derived>(p);
}

The solution to make static_pointer_cast a customization point is to add a generic declaration (no definition) of static_pointer_cast in a namespace (like std) and apply "using std::static_pointer_cast" declaration to activate ADL:

namespace std{

template<typename U, typename T>
unspecified
static_pointer_cast(T&&) = delete;

}

template<class BasePtr>
void generic_ptr_algo(BasePtr p)
{
  typedef std::pointer_traits<BasePtr>::template
     rebind<Derived> DerivedPtr;

  //ADL applies because static_pointer_cast is made
  //  visible according to [temp.arg.explicit]/8
  using std::static_pointer_cast;

  DerivedPtr dp = static_pointer_cast<Derived>(p);

  //...
}

A complete solution will need also the definition of static_pointer_cast for raw pointers, and this definition has been present in Boost (http://www.boost.org/boost/ pointer_cast.hpp) for years.

[ 2010-03-26 Daniel made editorial adjustments to the proposed wording. ]

[ Moved to NAD Future at 2010-11 Batavia ]

This is a new feature rather than a defect. It can be added later: "this is such a hairy area that people will put up with changes"

Proposed resolution:

Add to section 23.2 [utility] Utility components, Header <utility> synopsis:

// 20.3.X, generic pointer cast functions

template<typename U, typename T>
unspecified
static_pointer_cast(T&&) = delete;

template<typename U, typename T>
unspecified
dynamic_pointer_cast(T&&) = delete;

template<typename U, typename T>
unspecified
const_pointer_cast(T&&) = delete;

//Overloads for raw pointers
template<typename U, typename T>
auto static_pointer_cast(T* t) -> decltype(static_cast<U*>(t));

template<typename U, typename T>
auto dynamic_pointer_cast(T* t) -> decltype(dynamic_cast<U*>(t));

template<typename U, typename T>
auto const_pointer_cast(T* t) -> decltype(const_cast<U*>(t));

Add to section 23.2 [utility] Utility components, a new subclause 20.3.X Pointer cast utilities [pointer.cast]:

20.3.X Pointer cast utilities [pointer.cast]

1 The library defines generic pointer casting function templates so that template code can explicitly make these names visible and activate argument-dependent lookup for pointer cast calls.

//Generic declarations
template<typename U, typename T>
unspecified
static_pointer_cast(T&&) = delete;

template<typename U, typename T>
unspecified
dynamic_pointer_cast(T&&) = delete;

template<typename U, typename T>
unspecified
const_pointer_cast(T&&) = delete;

2 The library also defines overloads of these functions for raw pointers.

//Overloads for raw pointers
template<typename U, typename T>
auto static_pointer_cast(T* t) -> decltype(static_cast<U*>(t));

Returns: static_cast<U*>(t)

template<typename U, typename T>
auto dynamic_pointer_cast(T* t) -> decltype(dynamic_cast<U*>(t));

Returns: dynamic_cast<U*>(t)

template<typename U, typename T>
auto const_pointer_cast(T* t) -> decltype(const_cast<U*>(t));

Returns: const_cast<U*>(t)

[Example:

#include <utility> //static_pointer_cast
#include <memory>  //pointer_traits

class Base{};
class Derived : public Base{};

template<class BasePtr>
void generic_pointer_code(BasePtr b)
{
   typedef std::pointer_traits<BasePtr>::template
      rebind<Derived> DerivedPtr;

   using std::static_pointer_cast;
   //ADL applies now that static_pointer_cast is visible
   DerivedPtr d = static_pointer_cast<Derived>(b);
}

end example]

Replace in section 20.5.3.5 [allocator.requirements] Table 40 — Allocator requirements, the following table entries for allocator pointers:

Table 40 — Allocator requirements
Expression Return type Assertion/note
pre-/post-condition
Default
static_pointer_cast<X::pointerT>(w) X::pointer static_pointer_cast<X::pointerT>(w) == p  
static_pointer_cast<X::const_pointerconst T>(w) X::const_pointer static_pointer_cast<X::const_pointerconst T>(z) == q