// 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.

[[caveats]]
# Caveats and Caution

There are a few places where the rules of {cpp} (or a poorly implemented C library) can cause problems with this abstraction and thus force you to rewrite some of your code.

[[caveats.if]]
## `if` statement ordering and temporaries

`out_ptr` and `inout_ptr` work primarily by creating a temporary that, by the ironclad rules of Standard {cpp}, will destruct by the end of the enclosing function expression. The only time this rule is bent is for the sake of `if` statements, as written https://devblogs.microsoft.com/oldnewthing/20190429-00/?p=102456[by Raymond Chen] and demonstrated here. Consider the following fictional but representative call:

[source, cpp]
----
using zop = ztd:out_ptr;
// ...
if (my_maybe_failing_C_api(
	zop::out_ptr(my_handle), 56, ID_SOFT_CLS) == 0 
	&& my_handle) {
		// do something here if the function returns success (0) 
		// and "my_handle" is non-null
}
----

The problem is that all temporaties -- including the one `ztd::out_ptr::out_ptr` makes -- does not get destructed until the first `{` in that code. This means that the `.reset()` or equivalent functionality from the temporary's destructor is not executed until after the entire `if` statement is evaluated, meaning that even if the function success `my_handle` will contain its old value (e.g., `nullptr`) and may return false.

It is still safe to use such a construct with if statements if you will check the return value only, and not check the actual pointer itself in the same if statement:

----
using zop = ztd:out_ptr;
// ...
if (SQLITE_OK != sqlite3_open("test.db", 
	zop::out_ptr(database, sqlite3_close))) {
	// this usage is just fine
}
----

{cpp}17's if-initializer statement standardized the same mistake. The if-initializer's temporaries do not die even at a semi-colon:

----
using zop = ztd:out_ptr;
if (c_error_t err = my_maybe_failing_C_api(
	zop::out_ptr(my_handle), 56, ID_SOFT_CLS); 
	err == 0 && my_handle) {
		// still wrong
}
----

Everywhere else in the language, temporaries stop at the semicolon: that is not the case here. It is unfortunate, but that is how it works. Please use `out_ptr` (and any RAII abstraction) outside of the context of if/else conditionals.


[[caveats.poor_c]]
## Poorly designed C APIs

ManyC APIs will set the user-provided output pointer argument to `NULL`/`nullptr` upon invalid parameters or failure to complete its designated task. However, some C libraries do the opposite: they guarantee that the parameter will be unchanged, no matter what the incoming value is or whether or not the API suceeded or not. Defensively, you can make sure that in these cases when using <<overview.adoc#overview.inout_ptr, `out_ptr`>>, your smart pointer is already initialized with `nullptr` (as it is in the default case). If you are using a previously-allocated pointer with `out_ptr`, there is no need to worry because the default implementation will call `.reset()` on the type with a `nullptr` that was never changed. This is actually a tiny bit (but still measurable) performance drag and perhaps even a pessimization: more work is being done to try and zero-in on type of `out_ptr` that can take advantage of C APIs which will set the value to `nullptr` on failure of output in the non-reallocating case.

Worse, some re-allocating APIs (for use with <<overview.adoc#overview.inout_ptr, `inout_ptr`>>) will delete the pointer but not set the input `$$T**$$` argument to `nullptr`, leaving the abstraction and smart pointer to believe that it needs to delete the value once more.

It is impossible for this abstraction to handle such wild cases. Perhaps in the future additional types can be provided for "misbehaving" APIs, but they are thankfully few and far between so as not to cause this problem often.


[[caveats.poor_cxx]]
## Poorly designed {cpp} API

Note that a {cpp} API that takes a `$$T**$$` may throw. The API could throw after deleting / freeing the passed-in resoure, but before setting it to `nullptr`. Therefore, given a `really::terrible::api`, this can result in a double-delete:

[source, cpp]
----
namespace really::terrible { // C++17 nested namespaces
	inline int api (int** res) {
		delete *res;
		do_something_that_throws();
		*res = nullptr;
		/* other stuff */
		return 0;
	}
}

int main () {
	using zop = ztd::out_ptr;
	// leaks because of do_something_that_throws
	auto result = really::terrible::api(zop::inout_ptr(my_smart));
}
----

The workaround is not using this kind of API and filing a bug with the person who wrote it / maintains it.


[[caveats.general_order]]
# General Order of Evaluations problems

{cpp} function calls are indeterminately sequenced in a non-interleaving way from left to right. (There was a paper to fix this; it was stopped at the last second in a {cpp} Standards Plenary meeting for inertia and platform specific reasons. Thusly, we are in a situation wherethat mouthful of an opening sentence is the best "guarantee" you'll get about general function calls.

As such, you must ensure that construction of the smart pointer and checking for things like whether the smart pointer's contained pointer is the same (with a `== nullptr` or `explicit operator bool` conversion) or whether or not the function finished successfully are in separate expressions. Normal usage makes this a hard pit to fall into: please try not to be creative with your <<caveats.adoc#caveats.if, if statements>> and usage of this.


[[caveats.order]]
## Order of Evaluation with `ztd::out_ptr::inout_ptr`

Because `ztd::out_ptr::inout_ptr` has an unspecified internal implementation that may call `.release()` in the constructor, one could run into a problem where order of evaluation matters.

For ztd.out_ptr, the implementation of `inout_ptr` performs the release call in the destructor to avoid such a problem in a wide variety of cases. However, a user providing their own `inout_ptr_t` may behave differently than the specification: if you are a user, please be aware of this feature if you are providing a customization point.

For example, a misbehaving `inout_ptr_t` implementation may make it so the following is a footgun:

[source, cpp]
----
my_c_api(ztd::out_ptr::inout_ptr(my_smart), 
	24, 
	// unspecified into undefined behavior below: bad 
	// implementation called .release() in constructor and
	// the first argument was evaluated before this one
	// (e.g., in MSVC as opposed to GCC or Clang)
	my_smart->a_value,
	nullptr
);
----

This is an inherent flaw with all order-dependent function calls, but there is little that can be done until the Standard chooses a well-defined argument order.
