// Copyright ⓒ 2018-2021 ThePhD.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
//  See https://github.com/ThePhD/out_ptr/blob/master/docs/out_ptr.adoc for documentation.

[[customization]]
# Customization for Your Smart Pointers

Both `ztd::out_ptr::out_ptr` and `ztd::out_ptr::inout_ptr` have expectations on the Smart Pointer passed into it as the first argument.

In general, `ztd::out_ptr::out_ptr` needs the following 2 things:
- A `.get()` call, to retrieve the underlying pointer;
- and, one of `.reset(pointer, Args...)` or `SmartPointer(pointer, Args...)` to be a well-formed expression.

Additionally, `ztd::out_ptr::inout_ptr` has one more additional requirement:
- have a `.release()` function to release ownership of the resource so that utilized API can delete it and then re-allocate it.

Therefore, if you have a `.get()` and `.reset()` call, alongside a `.release()` for `inout_ptr`, you are completely fine with the default implementation.


[[customization.point]]
## Changing the Defaults

A user can change the default behavior of these types for their classes, so long as they follow the 4 functions specified for either <<reference/out_ptr.adoc#ref.out_ptr.class, `ztd::out_ptr::out_ptr_t`>> or <<reference/inout_ptr.adoc#ref.inout_ptr.class, `ztd::out_ptr::inout_ptr_t`>>. Let us consider a hypothetical class `handle` in a library and namespace named `phd`, where it is a more specialized version of `std::unique_ptr` that does not necessarily assume pointer semantics for the type. It allows one to customize how nullability is checked, what null value is written into the type, and more by the deleter (to make it work for an e.g. `int`):

[source, cpp]
----
namespace ztd {

	template <typename T>
	struct default_handle_deleter {
		using pointer = T;

		static void write_null(pointer& p) noexcept;
		static bool is_null(const pointer& p) noexcept;
		void operator()(const pointer&) const noexcept;
	};

	template <typename T, typename Deleter = default_handle_delete<T>>
	struct handle {
		using pointer = /*...*/;
		using deleter_type = Deleter;
		
		handle() noexcept;
		handle(pointer h) noexcept;
		handle(std::nullptr_t) noexcept;
		handle(pointer h, deleter_type d) noexcept;
		handle(std::nullptr_t, deleter_type d) noexcept;

		pointer& get() noexcept;
		const pointer& get() const noexcept;

		void reset() noexcept;
		void reset(pointer h) noexcept;

		pointer get_null() const noexcept;
		bool is_null() const noexcept;
	};

} // namespace ztd
----

This type may require special support. For example, this might represent a handle to a resource, but that handle is only a `uint32` (e.g., `GLuint` for OpenGL and similar). There are 2 levels of customization support: one for specializing a struct to control every detail, and one to control just a few static free functions within a traits type (similary to how `std::allocator_traits` behaves). A user may control one, or the other. The struct specialization has more control and is lower-level, and is checked in precedence and the customization point using traits. The traits customization point can be simpler and take less lines to write.


[[customization.point.traits]]
## Customization: High-Level Traits Specialization

A user may _(partially) template specialize_ the `ztd::out_ptr::out_ptr_traits` and the `ztd::out_ptr::inout_ptr_traits`. These types offer less control over the layout and internals of the RAII structures that are used to power the abstraction, while giving users the benefit of only having to override a small select portion of functionality. To use this traits type:

- First, open up the ztd.out_ptr namespace:
[source, cpp]
----
namespace ztd { namespace out_ptr {

}} // namespace ztd::out_ptr
----

- Then, create a _partial template specialization_:
[source, cpp]
----
namespace ztd { namespace out_ptr {
	
	template <typename T, typename Del, typename Pointer>
	class inout_ptr_traits<ztd::handle<T, Del>, Pointer> {
		/* ... */
	};

}} // namespace ztd::out_ptr
----

- Now, we need to create a handful of **required** functions. We need... 
* a `construct` function that takes the smart pointer (the handle) and all the Arguments that were stored from the initial call to `ztd::out_ptr::inout_ptr( ... )`;
* and, a `reset()` that takes all the necessary arguments and calls `.reset()`.

[source,cpp]
----
namespace ztd { namespace out_ptr {
	
	template <typename T, typename Del, typename Pointer>
	class inout_ptr_traits<ztd::handle<T, Del>, Pointer> {
		using Smart = ztd::handle<T, Del>;

	public:
		static Pointer construct (Smart& s) noexcept;
		static Pointer construct (Smart& s) noexcept;
	};

}} // namespace ztd::out_ptr
----

By using overloading, you can write as many or as little `construct` functions as you want that assert that a certain number or certain types of arguments are present:

[[customization.point.struct]]
## Customization: Low-Level Struct Specialization

A user may _(partially) template specialize_ the customized `ztd::out_ptr::out_ptr_t` and `ztd::out_ptr::inout_ptr_t`. It allows the user to control what gets stored and how simple it is. Here is an example for `inout_ptr_t`:

- First, open up the ztd.out_ptr namespace:
[source,cpp]
----
namespace ztd { namespace out_ptr {

}} // namespace ztd::out_ptr
----

- Then, create a _partial template specialization_:
[source,cpp]
----
namespace ztd { namespace out_ptr {
	
	template <typename T, typename Del, typename Pointer, typename... Args>
	class inout_ptr_t<ztd::handle<T, Del>, Pointer, Args...> {
		/* ... */
	};

}} // namespace ztd::out_ptr
----

- Now, we need to create a family of required functions (4 functions). We need... 
* a constructor that takes the smart pointer (the handle) and all the Arguments that need to be stored for use later;
* a move constructor to transfer over all of the data
* a conversion operator to the `Pointer*`;
* and, a destructor that calls `.reset()`.

[source,cpp]
----
namespace ztd { namespace out_ptr {
	
	template <typename T, typename Del, typename Pointer, typename... Args>
	class inout_ptr_t<ztd::handle<T, Del>, Pointer, Args...> {

		inout_ptr_t(Smart& ptr, Args... args) noexcept;

		inout_ptr_t(inout_ptr_t&& right) noexcept;

		inout_ptr_t& operator=(inout_ptr_t&& right) noexcept;
		
		operator Pointer*() const noexcept;

		~inout_ptr_t() noexcept;
	}

}} // namespace ztd::out_ptr
----


### And That's it!

Either way you choose to specialize, that is everything you need to write. A full, optimized implementation for `ztd::out_ptr::inout_ptr_t` and `ztd::handle` can be seen in https://github.com/ThePhD/out_ptr/blob/master/examples/source/customization.handle.cpp[this example].

The work is identical for `inout_ptr_t`, but the requirements on the type for how the destructor behave is slightly different. It anticipates that the function handling the double-pointer output argument `$$T**$$` will also handle calling the deleter first before re-allocating properly; therefore, its only responsibility to make sure the pointer is properly reseated in the smart pointer. This is typically done by calling `.release()` on the smart pointer and then putting the new value back into the pointer. For this reason, `ztd::out_ptr::inout_ptr_t` cannot support shared pointers, because there is no way to release a pointer in a shared ownership model.

