19

In C++17, empty tag types in the standard library now have default constructors which are marked explicit, and are also = default. For example, std::piecewise_construct_t is now defined as

struct piecewise_construct_t { explicit piecewise_construct_t() = default; };

My question is simply, what is the reason for this change from C++14? What does an explicitly defaulted explicit default constructor (!) mean for an empty class?

(To avoid being marked as a dupe: this question from 2010 asks about the purpose of explicit default constructors, but that was pre-C++11 and a long time ago now so things have likely changed. This question is more recent, but the answer seems to suggest that aggregate initialization will be performed regardless of the presence of the defaulted constructor, so I'm curious as to the reason for this change in the latest standard.)

Tristan Brindle
  • 16,281
  • 4
  • 39
  • 82

1 Answers1

16

The rationale for the library change is in LWG 2510 "Tag types should not be DefaultConstructible":

std::experimental::optional, for certain reasons, specifies its nullopt type to not be DefaultConstructible. It doesn't do so for its tag type in_place_t and neither does the standard proper for any of its tag types. That turns out to be very unfortunate, consider the following:

#include <memory>
#include <array>

void f(std::array<int, 1>, int) {} // #1
void f(std::allocator_arg_t, int) {} // #2

int main()
{
  f({}, 666); // #3
}

The call at #3 is ambiguous. What's even worse is that if the overload #1 is removed, the call works just fine. The whole point of a tag type is that it either needs to mentioned in a call or it needs to be a forwarded argument, so being able to construct a tag type like that makes no sense.

The LWG issue evolved side-by-side with CWG 1518 "Explicit default constructors and copy-list-initialization", which has useful background.

Community
  • 1
  • 1
Casey
  • 41,449
  • 7
  • 95
  • 125
  • 1
    Hmm, do you know if LWG intended that #3 remain ambiguous? Because having an explicit default constructor on `allocator_arg_t` doesn't change that. It only changes the "what's even worse" part. – T.C. May 25 '17 at 02:10
  • @T.C. How is it ambiguous? Types with explicit constructors are not aggregates, and copy-list-initialization is ill-formed if it selects an explicit constructor. (And no, FWIW, I don't know LWG's intent for 2510. It was just before my time.) – Casey May 25 '17 at 07:04
  • Ah, I missed [dcl.init.list]/3.4: "Otherwise, if the initializer list has no elements and `T` is a class type with a default constructor, the object is value-initialized." That's unfortunate. – Casey May 25 '17 at 07:12
  • Copy-list-initialization is ill-formed if it selects an explicit constructor, but that rule doesn't affect the formation of implicit conversion sequences (which is what controls viability for overload resolution purposes). The relevant subclause is [over.ics.list]. – T.C. May 25 '17 at 07:43
  • But maybe they knew that, since they moved the 2510 wording at the same time as my `nullopt_t` wording (2736) that does explicitly say "no default constructor". /shrug – T.C. May 25 '17 at 07:49
  • Thanks Casey, awesome as always :) – Tristan Brindle May 25 '17 at 11:41