When should a =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 explicit on CTAD deduction guides, but the keyword has no normative effect there. You should omit explicit from 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 =delete a constructor, you’re usually doing it to “overrule” another, greedier constructor. Craft the explicit-ness of your deleted constructor to match the explicit-ness of the constructor you intend to overrule.

Posted 2025-11-24