Why can the aggreggate structure be initialized but not set using the same argument list as in parenthesis initialization?

This code seems to be:

#include <string> #include <vector> struct bla { std::string a; int b; }; int main() { std::vector<bla> v; v.emplace_back("string", 42); } 

it would be possible to work correctly in this case, but this is not so (and I understand why). Providing a bla constructor solves this, but eliminates type aggregation, which can have far-reaching consequences.

Is this oversight in the standard? Or am I missing some cases when it explodes on my face, or is it just not as useful as I think?

+5
source share
2 answers

23.2.1 / 15.5

T is EmplaceConstructible in X of args, for zero or more args arguments, means the following expression is well-formed:

allocator_traits<A>::construct(m, p, args)

23.2.1 / 15

[Note. The container calls allocator_traits<A>::construct(m, p, args) to build the element in p using args. The default construct in std::allocator will call ::new((void*)p) T(args) , but specialized distributors may choose a different definition. -end note]

So, by default, the dispenser uses a constuctor, changing this behavior can lead to loss of backward compatibility. You can read more in this answer fooobar.com/questions/112454 / ....

There is also the issue โ€œTowards Better Redirectsโ€ and some casual discussion about this future.

+4
source

Is this oversight in the standard?

It is considered an open defect in the standard, tracked as LWG # 2089 . But a good solution to this is elusive.

The main problem arises from the fact that you cannot just use bit-init lists perforce. Type initialization lists with constructors can actually hide constructors, so some constructors may not be possible to call through list initialization. This is a problem vector<int> v{1, 2}; . This creates a 2-element vector , not a 1-element vector, the only element of which is 2.

Because of this, you cannot use list initialization in general contexts, such as allocator::construct .

Which brings us to:

I would think that if possible, there is a SFINAE trick, otherwise resort to init, which also works for aggregates.

This will require a type of type is_aggregate . Which does not exist at present, and no one has proposed its existence. Of course, you could agree with is_constructible , as the proposed resolution states to this question. But there is a problem with this: it effectively creates an alternative to initializing lists.

Consider the vector<int> example from the previous one. {1, 2} interpreted as a two-element initializer_list . But through emplace it will be interpreted as a call to a dual-purpose constructor, since the is_constructible of these two elements will be true. And this causes this problem:

 vector<vector<float>> fvec; fvec.emplace(1.0f, 2.0f); vector<vector<int>> ivec; ivec.emplace(1, 2); 

They do two completely different things. In the case of fvec it initializes the list, because vector<float> not a constructive of two floats. In the case of ivec it calls the constructor, because vector<int> is a construct of two integers.

Therefore, you need to restrict the initialization of the list in allocator::construct only work if T is an aggregate.

And even if you did, you would have to distribute this SFINAE trick in all places where indirect initialization is used. This includes the constructors and replacements of any/variant/optional in_place , calls to make_shared/unique , etc., None of which use allocator::construct .

And this does not take into account user code where such indirect initialization is necessary. If users do not perform the same initialization as the standard C ++ library, people will be upset.

This is a key problem to solve in such a way as not to fork indirect initialization APIs into groups that do not allow aggregates and groups that do not. There are many possible solutions , and none of them are perfect.

+2
source

Source: https://habr.com/ru/post/1266728/


All Articles