Tuesday, October 4, 2011

template policies via ADL

I wrote a range library some years ago and then found a proposal to include a range library in the std library.

I liked the design and decided to implement the proposal. One of the coolest features of the proposal is how ADL and decltype are used to implement traits or policies.

Since that time I have continued to refine the use of ADL and decltype to produce policies with vastly improved flexibility, stability and usability then anything I have seen before.

For example (Note: for space reasons this code is incomplete and won't compile. A working version of unique_t called unique_resource is available here) if we start with an existing smart-pointer that has a policy trait for the type and the way to close the type:

// policy based type
template<typename T, typename Close>
class unique_t_classic;

// user code using that type
template<typename T, typename Close>
void function(unique_t_classic<T, Close>& t);

Then at some future time we wish to add an index trait to enable operator[] usage:

// new policy
template<
  typename T, 
  typename Close, 
  typename Index=DefaultIndex<T>>
class unique_t_classic;

Now all the code that was dependent on the original policy set will fail to compile.
Here is the way I would express this functionality today:

template<typename TypeTag>
class unique_t;

template<typename Tag>
void function(unique_t<Tag>& t);

Now we can explore three different implementations of this with different policies without changing any of the above. The initial implementation might look like this.

template<typename TypeTag>
class unique_t
{
public:
  // these functions are looked up via ADL
  // thus this will pick up the definition
  // of unique_t_invalid_value from whatever
  // namespace TypeTag is in, and since TypeTag
  // is not the actual type (in database terms
  // it is a dataless key to the traits) the
  // user controls which namespace the
  // traits are in
  typedef decltype(unique_t_invalid_value(TypeTag()))
  type;
  //...
  void reset() {
    if (t != unique_t_invalid_value(TypeTag())) {
      unique_t_reset(t, TypeTag());
      t = unique_t_invalid_value(TypeTag());
    }
  }
private:
  type t;
};

A user might write code like this:

// Usability is provided by allowing simple
// and static definitions of specific 
// template instantiations
namespace detail
{
namespace file
{
struct tag {};
HANDLE unique_t_invalid_value(tag &&)
{
  return INVALID_HANDLE_VALUE;
}
void unique_t_reset(HANDLE value, tag &&)
{
  make_winerror_if(!CloseHandle(value)).suppress();
}
}
}
typedef unique_t<detail::file::tag>
unique_file;

Now it is time to make some changes to unique.
This will add optional indexing capability:

template<typename TypeTag>
class unique_t
{
public:
  ...
  auto operator[](
    size_t at) -> decltype(
        unique_t_index(at, TypeTag())) {
    return unique_t_index(at, TypeTag());
  }
private:
  type t;
};

unique_file still works as before. It does not support operator[] because it does not define unique_t_index.
More dramatic changes can be made without breaking existing code. This will make the type trait optionally independent of the return value of unique_t_invalid_value:

//
// This is a completely optional type
// it merely provides a succinct way 
// to create a type with a typedef 
// of the correct name 
//
template<typename T>
struct unique_t_traits_builder {
  typedef T
  type;
};

namespace detail
{
//
// this implements the optional part.
//
// notice that neither of these functions
// are implemented, they are only used in 
// declspec so no implementation is referenced
//
// the users definition of unique_t_traits
// needs no implementation either.
//

// this is used if the user supplied a 
// unique_t_traits for the TypeTag
template<typename TypeTag>
auto resolve_unique_t_traits(
  size_t) -> decltype(unique_t_traits(TypeTag()));

// the is used if the user only supplied 
// unique_t_invalid_value for the TypeTag
template<typename TypeTag>
unique_t_traits_builder<unique_t_invalid_value(TypeTag())>
resolve_unique_t_traits(...);
}

template<typename TypeTag>
class unique_t
{
public:
  typedef decltype(
    detail::resolve_unique_t_traits(TypeTag()))
  traits;

  typedef typename traits::type
  type;
  // ...
  void reset() {
    if (t != unique_t_invalid_value(TypeTag())) {
      unique_t_reset(t, TypeTag());
      t = unique_t_invalid_value(TypeTag());
    }
  }
private:
  type t;
};

Viola! no change to user code was required even though the implementation changed dramatically and the traits are different. This provides stability and flexibility that previous policy patterns lacked.

No comments:

Post a Comment