<experimental/propagate_const> synopsisnamespace std {
namespace experimental::inline fundamentals_v3 {
template <class T> class propagate_const;
template <class T>
constexpr bool operator==(const propagate_const<T>& pt, nullptr_t);
template <class T>
constexpr bool operator==(nullptr_t, const propagate_const<T>& pu);
template <class T>
constexpr bool operator!=(const propagate_const<T>& pt, nullptr_t);
template <class T>
constexpr bool operator!=(nullptr_t, const propagate_const<T>& pu);
template <class T, class U>
constexpr bool operator==(const propagate_const<T>& pt,
const propagate_const<U>& pu);
template <class T, class U>
constexpr bool operator!=(const propagate_const<T>& pt,
const propagate_const<U>& pu);
template <class T, class U>
constexpr bool operator<(const propagate_const<T>& pt,
const propagate_const<U>& pu);
template <class T, class U>
constexpr bool operator>(const propagate_const<T>& pt,
const propagate_const<U>& pu);
template <class T, class U>
constexpr bool operator<=(const propagate_const<T>& pt,
const propagate_const<U>& pu);
template <class T, class U>
constexpr bool operator>=(const propagate_const<T>& pt,
const propagate_const<U>& pu);
template <class T, class U>
constexpr bool operator==(const propagate_const<T>& pt, const U& u);
template <class T, class U>
constexpr bool operator!=(const propagate_const<T>& pt, const U& u);
template <class T, class U>
constexpr bool operator<(const propagate_const<T>& pt, const U& u);
template <class T, class U>
constexpr bool operator>(const propagate_const<T>& pt, const U& u);
template <class T, class U>
constexpr bool operator<=(const propagate_const<T>& pt, const U& u);
template <class T, class U>
constexpr bool operator>=(const propagate_const<T>& pt, const U& u);
template <class T, class U>
constexpr bool operator==(const T& t, const propagate_const<U>& pu);
template <class T, class U>
constexpr bool operator!=(const T& t, const propagate_const<U>& pu);
template <class T, class U>
constexpr bool operator<(const T& t, const propagate_const<U>& pu);
template <class T, class U>
constexpr bool operator>(const T& t, const propagate_const<U>& pu);
template <class T, class U>
constexpr bool operator<=(const T& t, const propagate_const<U>& pu);
template <class T, class U>
constexpr bool operator>=(const T& t, const propagate_const<U>& pu);
template <class T>
constexpr void swap(propagate_const<T>& pt,
propagate_const<T>& pt2) noexcept(see below);
template <class T>
constexpr const T& get_underlying(const propagate_const<T>& pt) noexcept;
template <class T>
constexpr T& get_underlying(propagate_const<T>& pt) noexcept;
} // namespace experimental::inline fundamentals_v3
template <class T> struct hash;
template <class T>
struct hash<experimental::fundamentals_v3::propagate_const<T>>;
template <class T> struct equal_to;
template <class T>
struct equal_to<experimental::fundamentals_v3::propagate_const<T>>;
template <class T> struct not_equal_to;
template <class T>
struct not_equal_to<experimental::fundamentals_v3::propagate_const<T>>;
template <class T> struct less;
template <class T>
struct less<experimental::fundamentals_v3::propagate_const<T>>;
template <class T> struct greater;
template <class T>
struct greater<experimental::fundamentals_v3::propagate_const<T>>;
template <class T> struct less_equal;
template <class T>
struct less_equal<experimental::fundamentals_v3::propagate_const<T>>;
template <class T> struct greater_equal;
template <class T>
struct greater_equal<experimental::fundamentals_v3::propagate_const<T>>;
} // namespace std
propagate_constnamespace std::experimental::inline fundamentals_v3 {
template <class T> class propagate_const {
public:
using element_type = remove_reference_t<decltype(*declval<T&>())>;
constexpr propagate_const() = default;
propagate_const(const propagate_const& p) = delete;
constexpr propagate_const(propagate_const&& p) = default;
template <class U>
explicit(!is_convertible_v<U, T>)
constexpr propagate_const(propagate_const<U>&& pu);
template <class U>
explicit(!is_convertible_v<U, T>)
constexpr propagate_const(U&& u);
propagate_const& operator=(const propagate_const& p) = delete;
constexpr propagate_const& operator=(propagate_const&& p) = default;
template <class U>
constexpr propagate_const& operator=(propagate_const<U>&& pu);
template <class U>
constexpr propagate_const& operator=(U&& u);
explicit constexpr operator bool() const;
constexpr const element_type* operator->() const;
constexpr operator const element_type*() const; // Not always defined
constexpr const element_type& operator*() const;
constexpr const element_type* get() const;
constexpr element_type* operator->();
constexpr operator element_type*(); // Not always defined
constexpr element_type& operator*();
constexpr element_type* get();
constexpr void swap(propagate_const& pt) noexcept(is_nothrow_swappable<T>);
private:
T t_; //exposition only
};
} // namespace std::experimental::inline fundamentals_v3
propagate_const is a wrapper around a pointer-like object type T
which treats the wrapped pointer as a pointer to const when
the wrapper is accessed through a const access path.
T
T shall be a cv-unqualified pointer-to-object type or a cv-unqualified class type for which
decltype(*declval<T&>()) is an lvalue reference to object type; otherwise
the program is ill-formed.
propagate_const<const int*> is well-formed but propagate_const<int* const> is not.
T
If T is class
type then it shall satisfy the following requirements. In this subclause
t denotes an lvalue of type T, ct
denotes as_const(t).
T and const T shall be contextually convertible to bool.
If T is implicitly convertible to element_type*,
(element_type*)t == t.get() shall be true.
If const T is implicitly convertible to const element_type*,
(const element_type*)ct == ct.get() shall be true.
| Expression | Return type | Pre-conditions | Operational semantics |
|---|---|---|---|
t.get() |
element_type* |
||
ct.get() |
const element_type* or element_type* |
|
t.get() == ct.get(). |
*t |
element_type& |
t.get() != nullptr |
*t refers to the same object as *(t.get()) |
*ct |
const element_type& or element_type& |
ct.get() != nullptr |
*ct refers to the same object as *(ct.get()) |
t.operator->() |
element_type* |
t.get() != nullptr |
t.operator->() == t.get() |
ct.operator->() |
const element_type* or element_type* |
ct.get() != nullptr |
ct.operator->() == ct.get() |
(bool)t |
bool |
|
(bool)t is equivalent to t.get() != nullptr |
(bool)ct |
bool |
|
(bool)ct is equivalent to ct.get() != nullptr |
is_constructible_v<T, U> is true.
t_ as if
direct-non-list-initializing an object of type T with the
expression std::move(pu.t_).
is_constructible_v<T, U> is true
and decay_t<U> is not a specialization of propagate_const.
t_ as if
direct-non-list-initializing an object of type T with
the expression std::forward<U>(u).
U is implicitly convertible to T.
t_ = std::move(pu.t_).*this.U is implicitly convertible to T and
decay_t<U> is not a specialization of propagate_const.
t_ = std::forward<U>(u).*this.(bool)t_.get() != nullptr.get().T is an object pointer type or
has an implicit conversion to const element_type*.
get().get() != nullptr.*get().t_ if T is an object pointer type,
otherwise t_.get().
get() != nullptr.get().T is an object pointer type or
has an implicit conversion to element_type*.
get().get() != nullptr.*get().t_ if T is an object pointer type,
otherwise t_.get().
T are swappable
(swap(t_, pt.t_).pt.t_ == nullptr.nullptr == pt.t_.pt.t_ != nullptr.nullptr != pt.t_.pt.t_ == pu.t_.pt.t_ != pu.t_.pt.t_ < pu.t_.pt.t_ > pu.t_.pt.t_ <= pu.t_.pt.t_ >= pu.t_.pt.t_ == u.pt.t_ != u.pt.t_ < u.pt.t_ > u.pt.t_ <= u.pt.t_ >= u.t == pu.t_.t != pu.t_.t < pu.t_.t > pu.t_.t <= pu.t_.t >= pu.t_.is_swappable_v<T> is true.pt1.swap(pt2).noexcept is equivalent to:
noexcept(pt1.swap(pt2))
Access to the underlying object pointer type is through free functions rather than member functions. These functions are intended to resemble cast operations to encourage caution when using them.
The specialization hash<experimental::fundamentals_v3::propagate_const<T>>
is enabled (hash<T> is enabled.
When enabled, for an object p of type propagate_const<T>,
hash<experimental::fundamentals_v3::propagate_const<T>>()(p)
evaluates to the same value as hash<T>()(p.t_).
For objects p, q of type propagate_const<T>,
equal_to<experimental::fundamentals_v3::propagate_const<T>>()(p,
q)
shall evaluate to the same value as equal_to<T>()(p.t_,
q.t_).
equal_to<T> is well-formed.
equal_to<T> is well-defined.
For objects p, q of type propagate_const<T>,
not_equal_to<experimental::fundamentals_v3::propagate_const<T>>()(p, q)
shall evaluate to the same value as not_equal_to<T>()(p.t_, q.t_).
not_equal_to<T> is well-formed.
not_equal_to<T> is well-defined.
For objects p, q of type propagate_const<T>,
less<experimental::fundamentals_v3::propagate_const<T>>()(p, q)
shall evaluate to the same value as less<T>()(p.t_, q.t_).
less<T> is well-formed.
less<T> is well-defined.
For objects p, q of type propagate_const<T>,
greater<experimental::fundamentals_v3::propagate_const<T>>()(p, q)
shall evaluate to the same value as greater<T>()(p.t_, q.t_).
greater<T> is well-formed.
greater<T> is well-defined.
For objects p, q of type propagate_const<T>,
less_equal<experimental::fundamentals_v3::propagate_const<T>>()(p, q)
shall evaluate to the same value as less_equal<T>()(p.t_, q.t_).
less_equal<T> is well-formed.
less_equal<T> is well-defined.
For objects p, q of type propagate_const<T>,
greater_equal<experimental::fundamentals_v3::propagate_const<T>>()(p, q)
shall evaluate to the same value as greater_equal<T>()(p.t_, q.t_).
greater_equal<T> is well-formed.
greater_equal<T> is well-defined.
<experimental/scope> synopsisnamespace std::experimental::inline fundamentals_v3 {
template <class EF>
class scope_exit;
template <class EF>
class scope_fail;
template <class EF>
class scope_success;
template <class R, class D>
class unique_resource;
template <class R, class D, class S=decay_t<R>>
unique_resource<decay_t<R>, decay_t<D>>
make_unique_resource_checked(
R&& r, const S& invalid, D&& d) noexcept(see below);
} // namespace std::experimental::inline fundamentals_v3
scope_exit, scope_fail, and scope_successThe class templates scope_exit, scope_fail,
and scope_success define scope guards that wrap a
function object to be called on their destruction.
In this subclause, the placeholder scope-guard
denotes each of these class templates. In descriptions of the
class members, scope-guard refers to the enclosing
class.
namespace std::experimental::inline fundamentals_v3 {
template <class EF> class scope-guard {
public:
template <class EFP>
explicit scope-guard(EFP&& f) noexcept(see below);
scope-guard(scope-guard&& rhs) noexcept(see below);
scope-guard(const scope-guard&) = delete;
scope-guard& operator=(const scope-guard&) = delete;
scope-guard& operator=(scope-guard&&) = delete;
~scope-guard () noexcept(see below);
void release() noexcept;
private:
EF exit_function; // exposition only
bool execute_on_destruction{true}; // exposition only
int uncaught_on_creation{uncaught_exceptions()}; // exposition only
};
template <class EF>
scope-guard(EF) -> scope-guard<EF>;
} // namespace std::experimental::inline fundamentals_v3
The class template scope_exit is a general-purpose
scope guard that calls its exit function when a scope is exited. The
class templates scope_fail and scope_success
share the scope_exit interface, only the situation when the
exit function is called differs.
void grow(vector<int>& v) {
scope_success guard([]{ cout << "Good!" << endl; });
v.resize(1024);
}scope_success
or scope_exit object refers to a local variable
of the function where it is defined, e.g., as a lambda capturing
the variable by reference, and that variable is used as a return
operand in that function, it is possible for that variable to already have been
returned when the scope-guard’s destructor
executes, calling the exit function. This can lead to surprising
behavior.
Template argument EF shall be a function object type
(EF
is an object type, it shall meet the g of type
remove_reference_t<EF>, the expression
g() shall be well-formed.
The constructor parameter f in the following constructors
shall be a reference to a function or a reference to a function
object (
is_same_v<remove_cvref_t<EFP>,
scope-guard> is false and
is_constructible_v<EF, EFP> is true.
f() is well-formed.
f() has well-defined behavior.
For scope_exit and scope_fail,
calling f() does not throw an exception.
EFP is not an lvalue reference type and
is_nothrow_constructible_v<EF, EFP>
is true, initialize exit_function
with std::forward<EFP>(f);
otherwise initialize exit_function with f.
For scope_exit and scope_fail,
if the initialization of exit_function throws an exception,
calls f().
scope_success, f() will not be
called if the initialization fails.exit_function.
(is_nothrow_move_constructible_v<EF> || is_copy_constructible_v<EF>)
is true.
EF is an object type:
is_nothrow_move_constructible_v<EF> is true,
EF meets the EF meets the is_nothrow_move_constructible_v<EF> is true,
initializes exit_function with std::forward<EF>(rhs.exit_function),
otherwise initializes exit_function with rhs.exit_function.
Initializes execute_on_destruction from rhs.execute_on_destruction and
uncaught_on_creation from rhs.uncaught_on_creation.
If construction succeeds, call rhs.release().
execute_on_destruction yields the value rhs.execute_on_destruction
yielded before the construction. uncaught_on_creation yields the value
rhs.uncaught_on_creation yielded before the construction.
exit_function.
noexcept is equivalent to:
is_nothrow_move_constructible_v<EF> || is_nothrow_copy_constructible_v<EF>
if (execute_on_destruction)
exit_function();
if (execute_on_destruction && uncaught_exceptions() > uncaught_on_creation)
exit_function();
if (execute_on_destruction && uncaught_exceptions() <= uncaught_on_creation)
exit_function();
noexcept(exit_function()) is false,
exit_function() can throw an exception,
notwithstanding the restrictions of exit_function().
execute_on_destruction = false.
unique_resourcenamespace std::experimental::inline fundamentals_v3 {
template <class R, class D> class unique_resource {
public:
unique_resource();
template <class RR, class DD>
unique_resource(RR&& r, DD&& d) noexcept(see below);
unique_resource(unique_resource&& rhs) noexcept(see below);
~unique_resource();
unique_resource& operator=(unique_resource&& rhs) noexcept(see below);
void reset() noexcept;
template <class RR>
void reset(RR&& r);
void release() noexcept;
const R& get() const noexcept;
see below operator*() const noexcept;
R operator->() const noexcept;
const D& get_deleter() const noexcept;
private:
using R1 = conditional_t<is_reference_v<
R>, reference_wrapper<remove_reference_t<R>>, R>; // exposition only
R1 resource; // exposition only
D deleter; // exposition only
bool execute_on_reset{true}; // exposition only
};
template<class R, class D>
unique_resource(R, D) -> unique_resource<R, D>;
} // namespace std::experimental::inline fundamentals_v3
unique_resource is a universal RAII wrapper for resource handles.
Typically, such resource handles are of trivial type and come with a factory function
and a clean-up or deleter function that do not throw exceptions. The clean-up function
together with the result of the creation function is used to create a unique_resource
variable, that on destruction will call the clean-up function. Access to the underlying
resource handle is achieved through get() and in case of a pointer type
resource through a set of convenience pointer operator functions.
The template argument D shall meet the requirements of a
d of type D and a lvalue r of
type R, the expression d(r) shall be well-formed.
D shall either meet the D shall meet the is_nothrow_move_constructible_v<D> shall be true.
For the purpose of this subclause, a resource type T
is an object type that meets the requirements of is_nothrow_move_constructible_v<T> is true,
or is an lvalue reference to a resource type. R shall be a resource type.
For the scope of the adjacent subclauses,
let RESOURCE be defined as follows:
resource.get() if is_reference_v<R> is true,resource otherwise.is_default_constructible_v<R> &&
is_default_constructible_v<D> is true.
resource and deleter;
execute_on_reset is initialized with false.
is_constructible_v<R1, RR> &&
is_constructible_v<D , DD> &&
(is_nothrow_constructible_v<R1, RR> || is_constructible_v<R1,RR&>) &&
(is_nothrow_constructible_v<D , DD> || is_constructible_v<D ,DD&>)
is true.
R1
or D is a specialization of reference_wrapper.
d(r), d(RESOURCE)
and deleter(RESOURCE) are well-formed.
d(r), d(RESOURCE)
or deleter(RESOURCE) has well-defined behavior and
does not throw an exception.
is_nothrow_constructible_v<R1, RR> is true,
initializes resource with std::forward<RR>(r),
otherwise initializes resource with r.
Then, if is_nothrow_constructible_v<D, DD> is true,
initializes deleter with std::forward<DD>(d),
otherwise initializes deleter with d.
If initialization of resource throws an exception,
calls d(r).
If initialization of deleter throws an exception, calls d(RESOURCE).
resource or deleter.
noexcept is equivalent to:
(is_nothrow_constructible_v<R1, RR> || is_nothrow_constructible_v<R1, RR&>) &&
(is_nothrow_constructible_v<D , DD> || is_nothrow_constructible_v<D , DD&>)
resource as follows:
is_nothrow_move_constructible_v<R1> is
true, from std::move(rhs.resource);rhs.resource.resource throws an exception,
rhs is left owning the resource and will free it in due time.
deleter as follows:
is_nothrow_move_constructible_v<D> is
true, from std::move(rhs.deleter);rhs.deleter.deleter throws an exception and
is_nothrow_move_constructible_v<R1> is true
and rhs.execute_on_reset is true:
rhs.deleter(RESOURCE);
rhs.release();
Finally, execute_on_reset is initialized with
exchange(rhs.execute_on_reset, false).
noexcept is equivalent to:
is_nothrow_move_constructible_v<R1> && is_nothrow_move_constructible_v<D>
reset().is_nothrow_move_assignable_v<R1> is true,
R1 meets the R1
meets the is_nothrow_move_assignable_v<D> is true,
D meets the D meets the reset();
if constexpr (is_nothrow_move_assignable_v<R1>) {
if constexpr (is_nothrow_move_assignable_v<D>) {
resource = std::move(rhs.resource);
deleter = std::move(rhs.deleter);
} else {
deleter = rhs.deleter;
resource = std::move(rhs.resource);
}
} else {
if constexpr (is_nothrow_move_assignable_v<D>) {
resource = rhs.resource;
deleter = std::move(rhs.deleter);
} else {
resource = rhs.resource;
deleter = rhs.deleter;
}
}
execute_on_reset = exchange(rhs.execute_on_reset, false);
rhs intact and *this in the released state.
*this.noexcept is equivalent to:
is_nothrow_move_assignable_v<R1> && is_nothrow_move_assignable_v<D>
if (execute_on_reset) {
execute_on_reset = false;
deleter(RESOURCE);
}
resource is well-formed.
deleter(r) is well-formed.
deleter(r) has well-defined behavior
and does not throw an exception.
reset();
if constexpr (is_nothrow_assignable_v<R1&, RR>) {
resource = std::forward<RR>(r);
} else {
resource = as_const(r);
}
execute_on_reset = true;
If copy-assignment of resource throws an exception,
calls deleter(r).
execute_on_reset = false.resource.is_pointer_v<R> is true and
is_void_v<remove_pointer_t<R>> is false.
return *get();add_lvalue_reference_t<remove_pointer_t<R>>.
is_pointer_v<R> is true.
get().deleter.unique_resource creation(resource == invalid ? true : false) is well-formed.
(resource == invalid ? true : false)
has well-defined behavior and does not throw an exception.
std::forward<R>(resource), std::forward<D>(d),
and !bool(resource == invalid).
Any failure during construction of the return value will not call d(resource)
if bool(resource == invalid) is true.
fclose
when fopen fails.
auto file = make_unique_resource_checked(
::fopen("potentially_nonexistent_file.txt", "r"),
nullptr,
[](auto fptr){ ::fclose(fptr); });
#include <type_traits>
namespace std::experimental::inline fundamentals_v3 {
template <class> class invocation_type; // not defined
template <class F, class... ArgTypes> class invocation_type<F(ArgTypes...)>;
template <class> class raw_invocation_type; // not defined
template <class F, class... ArgTypes> class raw_invocation_type<F(ArgTypes...)>;
template <class T>
using invocation_type_t = typename invocation_type<T>::type;
template <class T>
using raw_invocation_type_t = typename raw_invocation_type<T>::type;
struct nonesuch;
template <template<class...> class Op, class... Args>
using is_detected = see below;
template <template<class...> class Op, class... Args>
inline constexpr bool is_detected_v
= is_detected<Op, Args...>::value;
template <template<class...> class Op, class... Args>
using detected_t = see below;
template <class Default, template<class...> class Op, class... Args>
using detected_or = see below;
template <class Default, template<class...> class Op, class... Args>
using detected_or_t = typename detected_or<Default, Op, Args...>::type;
template <class Expected, template<class...> class Op, class... Args>
using is_detected_exact = is_same<Expected, detected_t<Op, Args...>>;
template <class Expected, template<class...> class Op, class... Args>
inline constexpr bool is_detected_exact_v
= is_detected_exact<Expected, Op, Args...>::value;
template <class To, template<class...> class Op, class... Args>
using is_detected_convertible = is_convertible<detected_t<Op, Args...>, To>;
template <class To, template<class...> class Op, class... Args>
inline constexpr bool is_detected_convertible_v
= is_detected_convertible<To, Op, Args...>::value;
} // namespace std::experimental::inline fundamentals_v3
This subclause contains templates that may be used to transform one type to another following some predefined rule.
Each of the templates in this subclause shall be a
Within this section, define the invocation parameters of INVOKE(f, t1, t2, ..., tN) as follows,
in which T1 is the possibly cv-qualified type of t1
and U1 denotes T1& if t1 is an lvalue
or T1&& if t1 is an rvalue:
f is a pointer to a member function of a class T
the U1 followed by
the parameters of f matched by t2, ..., tN.
N == 1 and f is a pointer to member data of a class T
the U1.
f is a class object,
the t1, ..., tN
of the best viable function (t1, ..., tN
among the function call operators and surrogate call functions of f.
f
matching t1, ... tN.
In all of the above cases,
if an argument tI matches the ellipsis in the function's tI.
S is defined as
struct S {
int f(double const &) const;
void operator()(int, int);
void operator()(char const *, int i = 2, int j = 3);
void operator()(...);
};
INVOKE(&S::f, S(), 3.5) are (S &&, double const &).INVOKE(S(), 1, 2) are (int, int).INVOKE(S(), "abc", 5) are (const char *, int).
The defaulted parameter j does not correspond to an argument.INVOKE(S(), locale(), 5) are (locale, int).
Arguments corresponding to ellipsis maintain their types.| Template | Condition | Comments |
|---|---|---|
template <class Fn, class... ArgTypes>
|
Fn and all types in the parameter pack ArgTypes
shall be complete types, (possibly cv-qualified) void, or arrays of unknown bound.
|
see below |
template <class Fn, class... ArgTypes>
|
Fn and all types in the parameter pack ArgTypes
shall be complete types, (possibly cv-qualified) void,
or arrays of unknown bound.
|
see below |
Access checking is performed as if in a context unrelated to Fn and ArgTypes.
Only the validity of the immediate context of the expression is considered.
The member raw_invocation_type<Fn(ArgTypes...)>::type shall be defined as follows.
If the expression INVOKE(declval<Fn>(), declval<ArgTypes>()...)
is ill-formed when treated as an unevaluated operand (type. Otherwise:
R denote result_of_t<Fn(ArgTypes...)>.Ti be the INVOKE(declval<Fn>(), declval<ArgTypes>()...) .type shall name the function type R(T1, T2, ...).
The member invocation_type<Fn(ArgTypes...)>::type shall be defined as follows.
If raw_invocation_type<Fn(ArgTypes...)>::type does not exist, there shall be no member type.
Otherwise:
A1, A2, … denote ArgTypes...R(T1, T2, …) denote raw_invocation_type_t<Fn(ArgTypes...)>type shall name the function type R(U1, U2, …)
where Ui is decay_t<Ai> if declval<Ai>() is an rvalue
otherwise Ti.
struct nonesuch {
~nonesuch() = delete;
nonesuch(nonesuch const&) = delete;
void operator=(nonesuch const&) = delete;
};
nonesuch has no default constructor
(
template <class Default, class AlwaysVoid,
template<class...> class Op, class... Args>
struct DETECTOR { // exposition only
using value_t = false_type;
using type = Default;
};
template <class Default, template<class...> class Op, class... Args>
struct DETECTOR<Default, void_t<Op<Args...>>, Op, Args...> { // exposition only
using value_t = true_type;
using type = Op<Args...>;
};
template <template<class...> class Op, class... Args>
using is_detected = typename DETECTOR<nonesuch, void, Op, Args...>::value_t;
template <template<class...> class Op, class... Args>
using detected_t = typename DETECTOR<nonesuch, void, Op, Args...>::type;
template <class Default, template<class...> class Op, class... Args>
using detected_or = DETECTOR<Default, void, Op, Args...>;
// archetypal helper alias for a copy assignment operation:
template <class T>
using copy_assign_t = decltype(declval<T&>() = declval<T const &>());
// plausible implementation for the is_assignable type trait:
template <class T>
using is_copy_assignable = is_detected<copy_assign_t, T>;
// plausible implementation for an augmented is_assignable type trait
// that also checks the return type:
template <class T>
using is_canonical_copy_assignable = is_detected_exact<T&, copy_assign_t, T>;
// archetypal helper alias for a particular type member:
template <class T>
using diff_t = typename T::difference_type;
// alias the type member, if it exists, otherwise alias ptrdiff_t:
template <class Ptr>
using difference_type = detected_or_t<ptrdiff_t, diff_t, Ptr>;