When should a =delete’d constructor be explicit?
=delete’d constructor be explicit?C++20 added atomic_ref<T> to the Standard Library (see
“Guard nouns or guard verbs?” (2023-11-11)).
But somehow C++20 overlooked atomic_ref<const T>; it’s finally permitted in C++26,
thanks to Gonzalo Brito Gadeschi’s P3323.
But then there was a new problem: since const T& binds to both lvalues and rvalues,
suddenly it is legal to write
auto ar = std::atomic_ref<const int>(42);
which will create a dangling reference.
Therefore, LWG 4472 proposes to add a deleted constructor to
atomic_ref:
template<class T>
struct atomic_ref {
constexpr explicit atomic_ref(T&);
explicit atomic_ref(T&&) = delete; // newly added
~~~~
};
Notice that the conversion from T& to atomic_ref<T> is already explicit, so we’re not worried here about
people accidentally binding an atomic_ref<const X> to a temporary X; they could only hit this deleted codepath
on purpose. So atomic_ref is fundamentally different from types like function_ref and string_view, which are
designed as implicit conversion targets and as “parameter-only types”
(see “Value category is not lifetime” (2019-03-11)).
Deleting this constructor of atomic_ref doesn’t really hurt anyone, although I question whether it helps anyone
either.
But there’s something interesting about this constructor: it is the Standard’s first deleted explicit constructor!
I wondered: If we’re =delete’ing it anyway, does it really matter whether it’s marked explicit or not?
That is, I wondered if deleted functions end up in a similar situation to CTAD deduction guides. C++’s grammar permits
expliciton CTAD deduction guides, but the keyword has no normative effect there. You should omitexplicitfrom deduction guides. But then, you shouldn’t use CTAD, either.
A correspondent on the cpplang Slack set me right: explicit can matter
to a deleted constructor. Godbolt:
template<class T>
struct AtomicRef {
explicit AtomicRef(T&);
MAYBE_EXPLICIT AtomicRef(T&&) = delete;
};
struct X {
X(int);
};
void f(AtomicRef<const int>);
void f(X);
void test() {
f(42);
}
Here, if MAYBE_EXPLICIT is defined away, the call to f becomes ambiguous: did we mean to call f(X)
(which would work) or f(AtomicRef<const int>) (which would be a case of
“I know what you’re trying to do, and you’re wrong”)?
Whereas if it’s defined as explicit, the call to f is unambiguous and OK: f(AtomicRef<const int>) isn’t a candidate
and f(X) is the only viable interpretation.
Notice that if AtomicRef(T&) were non-explicit (Godbolt),
then the call to f would be ambiguous no matter what, and this example would be irrelevant.
So yes, explicit can matter even to deleted constructors. It seems to me that a good rule of thumb is:
When you
=deletea constructor, you’re usually doing it to “overrule” another, greedier constructor. Craft theexplicit-ness of your deleted constructor to match theexplicit-ness of the constructor you intend to overrule.
