LWG 2089, Towards more perfect forwarding (original) (raw)
Document number: N4462
Ville Voutilainen
2015-04-07
Abstract
It has been known for quite some time that the library object factories (make_unique
, make_shared
, allocator::construct
) don't handle aggregates well, due to the factory functions requiring a parentheses-expression. There seems to be a simple and elegant fix that provides aggregate support without any loss of current functionality; (see the proposed resolution of LWG 2089) making such factories provide both braced-initialization and direct-initialization via a trait check for is_constructible
. This paper seeks to solicit EWG feedback on whether the proposed fix is the right solution going forward, or whether EWG thinks there are better alternatives.
Introduction
Currently, allocator::construct
is specified as follows:
template <class U, class... Args&> void construct(U* p, Args&&... args); Effects: ::new((void *)p) U(std::forward(args)...)
This doesn't handle an aggregate that is initializable withU{std::forward<Args>(args)...}
. Same problem applies to make_shared
and make_unique
. LWG 2089 proposes a fix that allows all the current code to work, and allows aggregates to work, too.
The gist of the proposed library fix is simple:
- if
is_constructible_v<TargetType, Args...>
, use direct-nonlist-initialization - otherwise, use brace-initialization.
The first bullet will check whether there's a constructor that can construct from the given arguments. An aggregate will not have such a constructor. Non-aggregates will hit the first bullet, aggregates will hit the second bullet.
Simple suggestion
The proposed resolution for LWG 2089
- allows aggregates to work with the library factories.
- adds no run-time overhead.
- doesn't break significant amounts of code (modulo expression SFINAE cases that are broken by any change, and are unlikely to be truly broken, since an overload that wasn't chosen previously because it wouldn't work will now be chosen and will now work. Also modulo cases where a user class befriended a library factory function so that the library factory function would've been able to invoke a private constructor, but I don't think the library specification guarantees that the library will not use internal helpers to do its work, so such techniques were never really guaranteed to work).
- is unlikely to significantly increase build times.
- is between trivial and easy to implement.
- requires no language changes.
Since it's somewhat unlikely that we will devise some magic-forwarding language facility, it certainly looks like the list above means the proposed resolution of LWG 2089 is an improvement, and we should just encourage LEWG/LWG to go forward with the proposed resolution of LWG 2089, keeping in mind that it needs to be applied to at least all ofallocator::construct
, allocator_traits::construct
, make_unique
and make_shared
. (uninitialized_copy
? uninitialized_fill
? Anything else? I can't find any others,EmplaceConstructible
is specified in terms of allocator::construct
, so emplace cases will Just Work). Note that std::experimental::optional
has a similar issue in its in-place constructor, so that should be considered separately, as it's in a different document.
Is that all?
Well, no. One of the reasons this paper is an EWG paper is that while the proposed resolution to LWG 2089 can fix the problem for library factories, and library vendors will certainly have little trouble implementing such a fix, user types don't really have a non-expert solution they can use. Teaching users that they need to eg. (*scary part begins*)delegate to a static member function template of a class template that takes a non-type bool parameter, fully specialize that class template for the false case, and dispatch on the result of is_constructible
(or dispatch to tagged overloads, with true_type
and false_type
)(*scary part ends*) is a fairly significant teachability issue. I don't personally look forward to teaching the bit marked with the *scary* markers - and I don't blame anyone who feels the same.
Some strawman ideas how to provide users with some help:
- Invent some better forwarding syntax that is aware of whether the intended initialization is to use braces or not (I don't know how; the syntax space is crowded)
- Add a library helper that will perform the initialization in the "right way" depending on
is_constructible
- in other words, expose parts of the implementation technique that library vendors are likely going to use, as a simple library function. - Other ways? How about allowing aggregates to be initialized with direct-non-list-initialization syntax?