Skip to content

Latest commit

 

History

History
88 lines (74 loc) · 2.57 KB

customisation_points.md

File metadata and controls

88 lines (74 loc) · 2.57 KB

Customisation point mechanism

This library makes use of a mechanism called tag_invoke() that allows types to customise operations by defining an overload of tag_invoke() that can be found by ADL that takes the customisation-point-object as the first parameter followed by the rest of the parameters passed to CPO::operator().

See P1895R0 for more details on tag_invoke.

Note that unifex::tag_invoke() is itself a customisation-point-object that forwards on to call the overload of tag_invoke() that is found by ADL. This handles the necessary mechanics needed to construct a Niebloid so that you don't have to do this for every CPO.

Customisations of CPOs using overloads of the tag_invoke() function typically define these overloads as friend-functions to limit the overload set that the compiler needs to consider when resolving a call to tag_invoke().

For example: Defining a new CPO

inline struct example_cpo {
  // An optional default implementation
  template<typename T>
  friend bool tag_invoke(example_cpo, const T& x) noexcept {
    return false;
  }

  template<typename T>
  bool operator()(const T& x) const
    noexcept(is_nothrow_tag_invocable_v<example_cpo, const T&>)
    -> tag_invoke_result_t<example_cpo, const T&> {
    // Dispatch to the call to tag_invoke() passing the CPO as the
    // first parameter.
    return tag_invoke(example_cpo{}, x);
  }
} example;

Example: Customising a CPO

struct my_type {
  friend bool tag_invoke(tag_t<example>, const my_type& t) noexcept {
    return t.isExample;
  }

  bool isExample;
}

Example: Calling a CPO

struct other_type {};

void usage() {
  // Customised for this type so will dispatch to custom implementation.
  my_type t{true};
  assert(example(t) == true);

  // Not customised so falls back to default implementation.
  other_type o;
  assert(example(t) == false);
}

This also allows wrapper types to forward through subsets of CPOs to the wrapped object.

template<typename T, typename Allocator>
struct allocator_wrapper {
  // Customise one CPO.
  friend void tag_invoke(tag_t<get_allocator>, const allocator_wrapper& a) {
    return a.alloc_;
  }

  // Pass through the rest.
  template<typename CPO, typename... Args>
  friend auto tag_invoke(CPO cpo, const allocator_wrapper& x, Args&&... args)
    noexcept(std::is_nothrow_invocable_v<CPO, const T&, Args...>)
    -> std::invoke_result_t<CPO, const T&, Args...> {
    return std::move(cpo)(x.inner_, (Args&&)...);
  }

  Allocator alloc_;
  T inner_;
};