explicit
Document #: | P3116R0 |
Date: | 2024-02-05 |
Project: | Programming Language C++ |
Audience: |
LEWG LWG |
Reply-to: |
Zach Laine <whatwasthataddress@gmail.com> |
[support]
(9
occurrences)[diagnostics]
(26
occurrences)[mem]
(25
occurrences)[utilities]
(83
occurrences)[strings]
(8
occurrences)[containers]
(87
occurrences)[iterators]
(10
occurrences)[ranges]
(138
occurrences)[numerics]
(90
occurrences)[time]
(47
occurrences)[localization]
(38
occurrences)[input.output]
(110
occurrences)[re]
(7
occurrences)[thread]
(43
occurrences)Initial revision.
Based on the recently-adopted P2267R1: Library Evolution Policies,
this paper makes the case for a policy for when and why to add
explicit
to member functions in
standard library templates and types.
There are 721 occurrences of the keyword
explicit
in the library clauses
of the standard. This does not include the appearance of the keyword in
plain text; this count is of in-code appearances only. This is an
overcount, though, as I overcount duplicates when an
explicit
member function is
listed in a synopsis and then again in a definition. However, this gives
the sense of how many times
explicit
is currently used.
These occurrences fall into three distinct categories:
explicit operator bool()
explicit
on
single-parameter constructors to prevent undesirable implicit
conversions.
explicit
on
zero-parameter constructors for tag types, to enforce spelling the tag
type’s name at the point of construction.
In hundreds of uses of
explicit
, I found almost no uses
of explicit
that did not fall
under one of the three categories above (except for views in
[ranges]
section, discussed
below). This is a pretty good indication that we already use
explicit
consistently, and that
formalizing this consistency would make a good policy.
P2267R1: Library Evolution Policies defines requirements for a new policy. Below is a discussion of the required elements, based on section 5.1.1 of that paper, using the same numbering from that paper.
We have consistent existing practice for how
explicit
is used in the standard
library. We also sometimes have to remind authors during design review
to use explicit
. Having a policy
would save LEWG effort by concretely indicating to paper authors when
and where explicit
should be
used. A policy will also help paper authors keep uses of
explicit
to the kinds of uses
indicated in the policy, rather than other ad hoc uses; this will
further help improve coherence.
This rationale applies only to turning the three categories of use
above into policy. There are some uses of
explicit
in the standard that
are exceptions to those categories.
The use of explicit
on
deduction guides (only three in the entire standard). This appears to be
addressed by LWG3451, and so this exception can be ignored.
The use of explicit
with
multi-parameter view constructors. This seems to be a special case; this
one of only two places I know about
explicit
is used on
multi-parameter constructors (the other is next in this list). This does
not seem like something that we should encourage in future proposals,
for the reasons outlined below, in the section on the
[ranges]
clause.
The conditional use of
explicit
with multi-parameter
span
constructors. As described
in [P1976R2], the conditional use of
explicit
is intended to make it
less likely for the user to write UB into their code when constructing a
non-dynamic_extent
span
from a pointer and size or
a pair of iterators. This also seems like a fairly particular use of
explicit
, ill-suited to be
turned into a broad rule.
The use of explicit
on
select conversion operators in classes in
[time]
. The usage there is to
make an explicit conversion possible with a cast, while disallowing that
conversion implicitly. This is certain to come up from time to time, but
not often. For now, it seems best not to try to make this use of
explicit
part of the policy. We
can always add it later if it becomes common.
The take-away here is that the three rules proposed in this paper are
applied with a high level of consistency in the standard today. Though
there are plenty of other uses of
explicit
, as outlined above,
none of those uses is contrary to the three rules proposed.
Here is a breakdown of the use of
explicit
in the library clauses,
clause by clause.
[support]
(9 occurrences)Used on
partial
/strong
/weak_ordering
,
e.g.:
constexpr explicit partial_ordering(ord v) noexcept;
explicit
is used to prevent
implicit conversions from (exposition-only
enum
)
ord
to
partial_ordering
.
[diagnostics]
(26 occurrences)Used on the constructors for various exception types that take a
string
, and the constructors of
various types that take an allocator, e.g.:
explicit logic_error(const string& what_arg);
explicit basic_stacktrace(const allocator_type& alloc) noexcept;
Used in explicit operator bool() const noexcept
.
[mem]
(25 occurrences)Used on various smart pointers’ constructors, e.g.:
template<class U> constexpr explicit unique_ptr(U p) noexcept;
template<class Y> explicit shared_ptr(Y* p);
Used in explicit operator bool() const noexcept
.
[utilities]
(83 occurrences)Used on the constructors of various utility templates, e.g.:
template<class... Args>
constexpr explicit optional(in_place_t, Args&&...);
template<size_t I, class... Args>
constexpr explicit variant(in_place_index_t<I>, Args&&...);
template<class T, class... Args>
explicit any(in_place_type_t<T>, Args&&...);
template<class Err = E>
constexpr explicit unexpected(Err&&);
template<class... Args>
constexpr explicit expected(in_place_t, Args&&...);
template<class charT, class traits, class Allocator>
constexpr explicit bitset(
const basic_string<charT, traits, Allocator>& str,
typename basic_string<charT, traits, Allocator>::size_type pos = 0,
typename basic_string<charT, traits, Allocator>::size_type n
= basic_string<charT, traits, Allocator>::npos,
= charT('0'),
charT zero = charT('1'));
charT one template<class T, class... Args>
explicit move_only_function(in_place_type_t<T>, Args&&...);
template<class T, class... Args>
explicit copyable_function(in_place_type_t<T>, Args&&...);
constexpr explicit basic_format_parse_context(basic_string_view<charT> fmt) noexcept;
template<class T> explicit basic_format_arg(T& v) noexcept;
Used on various exceptions’ constructors.
Used in explicit operator bool() const noexcept
.
[strings]
(8 occurrences)Used on basic_string
and
basic_string_view
constructors,
e.g.:
template<class R>
constexpr explicit basic_string_view(R&& r);
constexpr explicit basic_string(const Allocator& a) noexcept;
Used on two basic_string
deduction guides:
template<class charT,
class traits,
class Allocator = allocator<charT>>
explicit basic_string(basic_string_view<charT, traits>, const Allocator& = Allocator())
-> basic_string<charT, traits, Allocator>;
template<class charT,
class traits,
class Allocator = allocator<charT>>
(basic_string_view<charT, traits>,
basic_stringtypename see below::size_type, typename see below::size_type,
const Allocator& = Allocator())
-> basic_string<charT, traits, Allocator>;
These guides are part of the subject of LWG3451 (see below).
[containers]
(87 occurrences)Used on constructors that may take a single argument, e.g.:
explicit deque(const Allocator&);
explicit unordered_map(size_type n,
const hasher& hf = hasher(),
const key_equal& eql = key_equal(),
const allocator_type& a = allocator_type());
explicit queue(const Container& cont);
template<class... OtherIndexTypes>
constexpr explicit extents(OtherIndexTypes...) noexcept;
template<class... OtherIndexTypes>
constexpr explicit mdspan(data_handle_type ptr, OtherIndexTypes... exts);
Used conditionally on various
span
multi-argument
constructors:
template<class It>
constexpr explicit(extent != dynamic_extent) span(It first, size_type count);
template<class It, class End>
constexpr explicit(extent != dynamic_extent) span(It first, End last);
constexpr explicit(extent != dynamic_extent) span(R&& r);
constexpr explicit(extent != dynamic_extent) span(std::initializer_list<value_type> il);
template<class OtherElementType, size_t OtherExtent>
constexpr explicit(see below) span(const span<OtherElementType, OtherExtent>& s) noexcept;
Used in tag types like
sorted_t
.
Used in explicit operator bool() const noexcept
.
[iterators]
(10 occurrences)Used on various adapting iterators’ constructors, e.g.:
constexpr explicit reverse_iterator(Iterator x);
constexpr explicit back_insert_iterator(Container& x);
[ranges]
(138 occurrences)Used on various views’ constructors that may take a single argument, e.g.:
constexpr explicit single_view(const T& t) requires copy_constructible<T>;
constexpr explicit iota_view(W value);
Used on various views’ nested iterator and adaptor types’ constructors, e.g.:
// Within struct iota_view<W, Bound>::iterator.
constexpr explicit iterator(W value);
Used on the join_view
deduction guide:
template<class R>
explicit join_view(R&&) -> join_view<views::all_t<R>>;
This is the subject of LWG3451, “Inconsistently explicit deduction
guides”
(https://www.open-std.org/jtc1/sc22/wg21/docs/lwg-active.html#3451),
along with the basic_string
deduction guides mentioned previously. The issue recommends that the
explicit
be struck from all
three deduction guides. The issue seems to have been accepted as P3;
these three deduction guides seem to be safe to ignore.
Used in explicit operator bool() const noexcept
.
There are a few templates in this clause that have intentionally
non-explicit
single-argument
constructors:
template<class R, class Allocator = allocator<byte>>
(R&&, Allocator = Allocator()) -> elements_of<R&&, Allocator>;
elements_of
template<different-from<ref_view> T>
requires see below
constexpr ref_view(T&& t);
constexpr owning_view(R&& t);
There are also a few view templates in this clause that have
multi-argument constructors that are marked
explicit
:
constexpr explicit iota_view(type_identity_t<W> value, type_identity_t<Bound> bound);
constexpr explicit iota_view(iterator first, see below last);
constexpr explicit repeat_view(piecewise_construct_t,
<TArgs...> value_args, tuple<BoundArgs...> bound_args = tuple<>{});
tupleconstexpr explicit repeat_view(piecewise_construct_t,
<TArgs...> value_args, tuple<BoundArgs...> bound_args = tuple<>{});
tupleconstexpr explicit transform_view(V base, F fun);
constexpr explicit take_view(V base, range_difference_t<V> count);
constexpr explicit take_while_view(V base, Pred pred);
constexpr explicit drop_view(V base, range_difference_t<V> count);
constexpr explicit drop_while_view(V base, Pred pred);
constexpr explicit join_with_view(V base, Pattern pattern);
constexpr explicit lazy_split_view(R&& r, range_value_t<R> e);
constexpr explicit split_view(V base, Pattern pattern);
constexpr explicit adjacent_transform_view(V base, F fun);
constexpr explicit chunk_view(V base, range_difference_t<V> n);
constexpr explicit slide_view(V base, range_difference_t<V> n);
constexpr explicit chunk_by_view(V base, Pred pred);
constexpr explicit stride_view(V base, range_difference_t<V> stride);
This differs from other templates in the standard. There is an associated paper [P2711R1]: Making multi-param constructors of views explicit, which was discussed at the Kona 2022 meeting.
The intention here is to eliminate the difference in user code
between a single- and multi-argument call to one of these view’s
constructors. If they’re all
explicit
, then
foo_view x = {arg};
and
foo_view x = {arg1, arg2};
will
both be ill-formed, which is more consistent. As the paper points out,
users will almost always be using the range adaptors and not
constructing views directly, and the inconsistency problem itself it not
very serious.
[numerics]
(90 occurrences)Used on various templates in random number generation, e.g.:
explicit linear_congruential_engine(result_type s);
explicit bernoulli_distribution(double p);
Used on valarray
:
explicit valarray(size_t);
.
Used on various templates in
linalg
, e.g.
// inside template<class Layout> class layout_transpose
constexpr explicit mapping(const nested-mapping-type&);
[time]
(47 occurrences)Used on various time templates, e.g.:
template<class Rep2>
constexpr explicit duration(const Rep2& r);
constexpr explicit time_point(const duration& d);
constexpr explicit day(unsigned d) noexcept;
explicit
is also used on a
few conversion operators in this clause, e.g.:
constexpr explicit day::operator unsigned() const noexcept;
constexpr explicit month::operator unsigned() const noexcept;
constexpr year_month_day::operator sys_days() const noexcept;
constexpr explicit year_month_day::operator local_days() const noexcept;
constexpr year_month_day_last::operator sys_days() const noexcept;
constexpr explicit year_month_day_last::operator local_days() const noexcept;
The intention here appears to be to allow implicit conversions in some places, and to create other explicit conversions that require explicit opt-in, using a cast.
[localization]
(38
occurrences)Used on various locales, facets, etc., e.g.:
explicit locale(const char* std_name);
explicit collate_byname(const char*, size_t refs = 0);
[input.output]
(110
occurrences)Used on various exception templates, e.g.:
explicit ios_base::failure(const string& msg, const error_code& ec = io_errc::stream);
Used on various stream and buf templates, e.g.:
explicit basic_istream(basic_streambuf<charT, traits>* sb);
Used on various filesystem templates, e.g.:
explicit directory_iterator(const path& p);
Used in explicit operator bool() const noexcept
.
[re]
(7 occurrences)Used on an exception type:
explicit regex_error(regex_constants::error_type ecode);
Used on various regex templates, e.g.:
explicit basic_regex(const charT* p, flag_type f = regex_constants::ECMAScript);
explicit match_results(const Allocator& a);
[thread]
(43 occurrences)Used on various templates related to threads, e.g.:
explicit stop_source(nostopstate_t) noexcept;
template<class C>
explicit stop_callback(const stop_token& st, C&& cb)
template<class F, class... Args> explicit thread(F&& f, Args&&... args);
explicit atomic_ref(T&);
Used on various lock templates, e.g.:
explicit lock_guard(mutex_type& m);
explicit scoped_lock(MutexTypes&... m);
Used on an exception type:
explicit future_error(future_errc e);
Used on various other templates, e.g.:
constexpr explicit counting_semaphore(ptrdiff_t desired);
constexpr explicit latch(ptrdiff_t expected);
constexpr explicit barrier(ptrdiff_t expected,
= CompletionFunction());
CompletionFunction f
template<class F>
explicit packaged_task(F&& f);
I did not look at this in any depth. Anecdotally, it seems very
common to use explicit
on
constructors and bool
conversion
operators for disabling implicit conversions. I have rarely, if ever,
seen explicit
used to force the
naming of a tag type at the point of construction outside of the
standard.
When proposing new templates and types for the standard library,
explicit
should be used to
prevent unwanted implicit conversions, or to force the user to name a
type at the point of its use when constructing values of that type. In
particular, explicit
should be
applied in the following circumstances.
Place explicit
on
bool
conversion operators,
e.g. constexpr explicit operator bool() const;
.
Without this, the entire class type is convertible to
bool
. Example:
optional
’s
bool
conversion operator is
constexpr explicit operator bool() const noexcept
(see [optional.observe]). This makes
optional<T>
contextually
convertible to bool
, but it
cannot directly be used to initialize a
bool
.
Place explicit
on
constructors that may take a single parameter (including when defaulted
parameters are not passed), when implicit conversion from that single
parameter is undesirable. Implicit conversions should exist only between
types that are fundamentally the same (such as
float
and
double
, but not a
packaged_task
and some invocable
that may be used to construct it). Example: constexpr explicit vector(size_type n, const Allocator& = Allocator());
(see [vector.cons]).
Place explicit
on tag
types, to force the spelling of the tag type at the point of its
construction. Example:
explicit unexpect_t() = default
,
which prevents using unexpect_t
by simply writing {}
when
calling a function that takes an
unexpect_t
at that argument
position.
Append to “List of Standard Library Policies” section of SD-9:
explicit
on
bool
conversion operators.X+1. (P3116) Place explicit
on constructors that may be called with a single argument, when implicit
conversion from that single argument is undesirable.
X+2. (P3116) Place explicit
on nullary constructors of tag types.