According to [range.single.view#3], one of std::ranges::single_view constructors define as:
template<class... Args>
requires constructible_from<T, Args...>
constexpr explicit single_view(in_place_t, Args&&... args);
Effects: Initializes value_ as if by value_{in_place, std::forward<Args>(args)...}.
Why the standard specifies to use of direct-list-initialization ({}) to initialize value_? Why not use direct-initialization (()) just like std::optional, std::variant, and std::any?
Moreover, std::constructible_from(std::is_constructible) specifies that T obj(std::declval<Args>()...) is well-formed instead of T obj{std::declval<Args>()...}.
Consider the following:
ranges::single_view<std::vector<int>> sv(std::in_place, 100, 0);
std::cout << sv.begin()->size() << "\n"; // #1
std::optional<std::vector<int>> op(std::in_place, 100, 0);
std::cout << op->size() << "\n"; // #2
Because different initializations are used, #1 will call the std::vector<int>{0, 100} and print 2, and #2 will call the std::vector<int>(0, 100) and print 100.
Why does the standard specify using curly brace initialization for the underlying value even though it may cause inconsistencies? What is the consideration behind this?
value_ is a semiregular-box, so its constructors are well-known and don't include an initializer-list constructor. The inconsistency doesn't arise because in this case braces and parens are equivalent - the underlying type will always be constructed with parens, because that's what the semiregular-box's constructor is specified to do.
The behavior you are observing is a libstdc++ bug.
As to why the ranges clause uses list-initialization pervasively - there's no good reason, and it actually caused issues. LWG has approved a paper (P2367) that will remove misuses of list-initialization with normative impact; the remainder is tracked by editorial issue 4593.
#1 will call the std::vector{0, 100}
No, it won't.
value_ is of the type semiregular-box<T>, which is an exposition-only type that acts like optional<T>, but with some differences. But those differences don't apply here, so you can treat it as though it were just optional<T>.
An initializer_list can only be formed from a braced-init-list if all of the types of the expression are the same (or if they're convertible to some type without narrowing). But {in_place, std::forward<Args>(args)...} starts with the type std::in_place_t, which is almost certainly not one of the types of Args. So no initializer_list can be formed from it.
And even if a user does provide an in_place_t as Args (perhaps they're doing single_view<optional<T>> or something), it doesn't matter because optional (and therefore semiregular-box) doesn't have an initializer_list constructor. So it would call a matching regular constructor, just like if you'd used ().
Related
To be specific: direct-list-initialization (cppreference.com (3)).
Both std::make_shared and uniform initialization features were introduced in C++11. So we can use aggregate initialization when allocating objects on heap: new Foo{1, "2", 3.0f}. This is a nice way to directly initialize objects that have no constructors, such as aggregates, pods, etc.
A real-life scenarios, such as declaring casual structures within a function, to efficiently supply set of arguments to a lambda became very common, in my experience:
void foo()
{
struct LambdaArgs
{
std::string arg1;
std::string arg2;
std::string arg3;
};
auto args = std::make_shared<LambdaArgs>(LambdaArgs{"1", "2", "3"});
auto lambda = [args] {
/// ...
};
/// Use lambda
/// ...
}
Here auto args = std::make_shared<LambdaArgs>("1", "2", "3"); whould be nice but isn't going to work, because std::make_shared is usually implemented as:
template<typename T, typename... Args>
std::shared_ptr<T> make_shared(Args && ...args)
{
return std::shared_ptr<T>(new T(std::forward<Args>(args)...));
}
So we're stuck with the auto args = std::make_shared<LambdaArgs>(LambdaArgs{"1", "2", "3"});.
The problem that was supposed to be solved with std::make_shared still persists for object without constructor. And the workaround is not only unaesthetic but also less efficient.
Is this another oversight or are there some reasons that defend this choice. Specifically, what pitfalls can be in the list initialization solution? std::make_unique was introduced later, in C++14, why does it too follow same pattern?
Specifically, what pitfalls can be in the list initialization solution?
All of the typical pitfalls of using list-initialization.
For example, the hiding of non-initializer_list constructors. What does make_shared<vector<int>>(5, 2) do? If your answer is "constructs an array of 5 ints", that's absolute correct... so long as make_shared isn't using list-initialization. Because that changes the moment you do.
Note that suddenly changing this would break existing code, since right now all of the indirect initialization functions use constructor syntax. So you can't just change it willy-nilly and expect the world to keep working.
Plus one more unique to this case: the narrowing issue:
struct Agg
{
char c;
int i;
};
You can do Agg a{5, 1020}; to initialize this aggregate. But you could never do make_shared<Agg>(5, 1020). Why? Because the compiler can guarantee that the literal 5can be converted to a char with no loss of data. However, when you use indirect initialization like this, the literal 5 is template-deduced as int. And the compiler cannot guarantee that any int can be converted to a char with no loss of data. This is called a "narrowing conversion" and is expressly forbidden in list initialization.
You would need to explicitly convert that 5 to a char.
The standard library has an issue on this: LWG 2089. Though technically this issue talks about allocator::construct, it should equally apply to all indirect initialization functions like make_X and C++17's in-place constructors for any/optional/variant.
why does it too follow same pattern?
It follows the same pattern because having two different functions that look almost identical that have radically and unexpectedly different behaviors would not be a good thing.
Note that C++20 resolves the aggregate part of this issue at least by making constructor-style syntax invoke aggregate initialization if the initializers would have been ill-formed for regular direct initialization. So if T is some aggregate type (with no user-declared constructors), and T(args) wouldn't invoke a copy/move constructor (the only constructors that take arguments which a type with no user-declared constructors could have), then the arguments will instead be used to attempt to aggregate initialize the structure.
Since allocator::construct and other forms of forwarded initialization default to direct-initialization, this will let you initialize aggregates through forwarded initialization.
You still can't do other list-initialization stuff without explicitly using an initializer_list at the call site. But that's probably for the best.
The problem that was supposed to be solved with std::make_shared still persists for object without constructor.
No, the problem does not persist. The main problem make_shared is solving is a potential for a memory leak between the object is allocated and the ownership is taken by the smart pointer. It is also capable of removing one extra allocation for control block.
Yes, it is inconvenient to not be able to use a direct initialization, but this was never the declared goal of make_shared.
The question itself may be confusing, so I'll describe it in detail here. Suppose we have a type S that represents a 1-dimensional vector (the linear algebra one, not the std one). Since it's kind of an array, so it's good if it behaves like std::array<int, 1>. Obviously, we don't want to allow construction with just a scalar, but implicit list initialization from a single element should be allowed. Shown in the following code snippet are the few initialization/conversion scenarios, I would prefer #2 and #4 to work while rejecting #1 and #3.
struct S
{
explicit(?) S(int value);
};
S f1() { return 0; } // #1
S f2() { return { 0 }; } // #2
void g()
{
S s1 = 0; // #3
S s2{ 0 }; // #4
}
Making the constructor explicit would disallow #1 and #3, which is nice, but as a side effect, it also disallows #2. #2 should be allowed in this scenario, just like how std::array<int, 1> works.
Since the type would have other constructors defined, and I would like the data to be private, so AFAIK it's not possible to use the same technique as std::array (aggregate init + brace elision). Are there any means to achieve this goal? I don't mind if some bizarre tricks must be used.
Thanks!
Edit:
I have tried explicit(false) S(std::array<int, 1>); and it works, but it needs an additional pair of braces for cases #2 and #4. It would be great if the same goal can be achieved without the extra braces.
This isn't possible. Since S is not an aggregate and the initializer-list is not empty, you go straight to considering constructors for return {0};. That's the same overload resolution as for the (non-list) copy-initialization for return 0;, except that explicit constructors are disallowed rather than disregarded.
Oddly enough, this means you can have the inverse behavior: by providing an implicit and an explicit constructor that are ambiguous for construction from int, you can make return {0}; fail due to ambiguity while having return 0; select the converting constructor.
One might try a trick involving an intermediate type:
struct hold {hold(int);};
struct S {
S(hold);
};
Unfortunately, the rules for multiple user-defined conversions are also consistent between copy- and copy-list-initialization, so the S(hold(0)) interpretation won't be tried for either mode of construction. (There are cases where multiple levels of braces allow multiple user-defined conversions, but that's not relevant here by hypothesis.)
To be specific: direct-list-initialization (cppreference.com (3)).
Both std::make_shared and uniform initialization features were introduced in C++11. So we can use aggregate initialization when allocating objects on heap: new Foo{1, "2", 3.0f}. This is a nice way to directly initialize objects that have no constructors, such as aggregates, pods, etc.
A real-life scenarios, such as declaring casual structures within a function, to efficiently supply set of arguments to a lambda became very common, in my experience:
void foo()
{
struct LambdaArgs
{
std::string arg1;
std::string arg2;
std::string arg3;
};
auto args = std::make_shared<LambdaArgs>(LambdaArgs{"1", "2", "3"});
auto lambda = [args] {
/// ...
};
/// Use lambda
/// ...
}
Here auto args = std::make_shared<LambdaArgs>("1", "2", "3"); whould be nice but isn't going to work, because std::make_shared is usually implemented as:
template<typename T, typename... Args>
std::shared_ptr<T> make_shared(Args && ...args)
{
return std::shared_ptr<T>(new T(std::forward<Args>(args)...));
}
So we're stuck with the auto args = std::make_shared<LambdaArgs>(LambdaArgs{"1", "2", "3"});.
The problem that was supposed to be solved with std::make_shared still persists for object without constructor. And the workaround is not only unaesthetic but also less efficient.
Is this another oversight or are there some reasons that defend this choice. Specifically, what pitfalls can be in the list initialization solution? std::make_unique was introduced later, in C++14, why does it too follow same pattern?
Specifically, what pitfalls can be in the list initialization solution?
All of the typical pitfalls of using list-initialization.
For example, the hiding of non-initializer_list constructors. What does make_shared<vector<int>>(5, 2) do? If your answer is "constructs an array of 5 ints", that's absolute correct... so long as make_shared isn't using list-initialization. Because that changes the moment you do.
Note that suddenly changing this would break existing code, since right now all of the indirect initialization functions use constructor syntax. So you can't just change it willy-nilly and expect the world to keep working.
Plus one more unique to this case: the narrowing issue:
struct Agg
{
char c;
int i;
};
You can do Agg a{5, 1020}; to initialize this aggregate. But you could never do make_shared<Agg>(5, 1020). Why? Because the compiler can guarantee that the literal 5can be converted to a char with no loss of data. However, when you use indirect initialization like this, the literal 5 is template-deduced as int. And the compiler cannot guarantee that any int can be converted to a char with no loss of data. This is called a "narrowing conversion" and is expressly forbidden in list initialization.
You would need to explicitly convert that 5 to a char.
The standard library has an issue on this: LWG 2089. Though technically this issue talks about allocator::construct, it should equally apply to all indirect initialization functions like make_X and C++17's in-place constructors for any/optional/variant.
why does it too follow same pattern?
It follows the same pattern because having two different functions that look almost identical that have radically and unexpectedly different behaviors would not be a good thing.
Note that C++20 resolves the aggregate part of this issue at least by making constructor-style syntax invoke aggregate initialization if the initializers would have been ill-formed for regular direct initialization. So if T is some aggregate type (with no user-declared constructors), and T(args) wouldn't invoke a copy/move constructor (the only constructors that take arguments which a type with no user-declared constructors could have), then the arguments will instead be used to attempt to aggregate initialize the structure.
Since allocator::construct and other forms of forwarded initialization default to direct-initialization, this will let you initialize aggregates through forwarded initialization.
You still can't do other list-initialization stuff without explicitly using an initializer_list at the call site. But that's probably for the best.
The problem that was supposed to be solved with std::make_shared still persists for object without constructor.
No, the problem does not persist. The main problem make_shared is solving is a potential for a memory leak between the object is allocated and the ownership is taken by the smart pointer. It is also capable of removing one extra allocation for control block.
Yes, it is inconvenient to not be able to use a direct initialization, but this was never the declared goal of make_shared.
Aggregate initialization requires among other things no user-provided constructors. But std::tuple and std::pair pair have a large set of overloaded constructors. From the point of the core language, are these constructors user-provided or even user-declared ?
With C++17 it will be possible to write (update/clarification: where nocopy is a class that can not be copied or moved, such as std::mutex)
auto get_ensured_rvo_str(){
return std::pair(std::string(),nocopy());
}
edit: no, it's not possible as explained in the linked to answers and the answer below.
which requires aggregate initialization (for context: Multiple return values (structured bindings) with unmovable types and guaranteed RVO in C++17).
Are tuple and pair backed by special standard language to allow this (in presence of constructors) ? :
20.5.2.1 Construction
...
EXPLICIT constexpr tuple(const Types&...);
6
Effects: The
constructor initializes each element with the value of the
corresponding parameter.
or can we in principle write our own tuple or pair?
No, there is no support in tuple or pair for passing no-move types to their constructors, and as you've observed there cannot be, since the constructor argument and tuple (or pair) member can be observed to be different objects:
// exposition only
template<class... Us>
tuple(Us&&... us) : values{std::forward<Us>(us)...} {}
^^ these
^^^^^^ are different objects to these
You would have to use piecewise construction:
return std::pair<std::string, nocopy>(std::piecewise_construct,
std::forward_as_tuple(), std::forward_as_tuple());
Matt Calabrese made an interesting point on the std-proposals list that now we have guaranteed RVO it should be possible to write components that accept factories to construct their members effectively inplace:
// hypothetical factory constructor
return std::pair(std::factory_construct,
[] { return std::string{}; }, [] { return nocopy{}; });
Another possible direction would be to remove the constructors from tuple and pair (or, more realistically, to write workalike components without constructors) and rely on the new extensions to aggregate initialization that should permit aggregate initialization of tuple and pair implemented via multiple-inheritance. Example.
It was suggested by a team member that using an intializer like this:
return Demo{ *this };
was better than:
return Demo(*this);
Assuming a simple class like this:
class Demo {
public:
int value1;
Demo(){}
Demo(Demo& demo) {
this->value1 = demo.value1;
}
Demo Clone() {
return Demo{ *this };
}
};
I admit to having not seen the { *this } syntax before, and couldn't find a reference that explained it well enough that I understood how the two options differed. Is there a performance benefit, a syntax choice, or something more?
Your colleague is missing a trick with "uniform initialization", there is no need for the type-name when it is known. E.g. when creating a return value. Clone could be defined as:
Demo Clone() {
return {*this};
}
This will call the Demo copy constructor as needed. Whether you think this is better or not, is up to you.
In GOTW 1 Sutter states as a guideline:
Guideline: Prefer to use initialization with { }, such as vector v = { 1, 2, 3, 4 }; or auto v = vector{ 1, 2, 3, 4 };, because it’s more consistent, more correct, and avoids having to know about old-style pitfalls at all. In single-argument cases where you prefer to see only the = sign, such as int i = 42; and auto x = anything; omitting the braces is fine. …
In particular, using braces can avoid confusion with:
Demo d(); //function declaration, but looks like it might construct a Demo
Demo d{}; //constructs a Demo, as you'd expect
The brace syntax will use a constructor that takes an initializer list first, if one exists. Otherwise it will use a normal constructor. It also prevents the chance of the vexing parse listed above.
There is also different behaviour when using copy initialization. With the standard way
Demo d = x;
The compiler has the option to convert x to a Demo if necessary and then move/copy the converted r-value into w. Something similar to Demo d(Demo(x)); meaning that more than one constructor is called.
Demo d = {x};
This is equivalent to Demo d{x} and guarantees that only one constructor will be called. With both assignments above explicit constructors are cannot be used.
As mentioned in the comments, there are some pitfalls. With classes that take an initializer_list and have "normal" constructors can cause confusion.
vector<int> v{5}; // vector containing one element of '5'
vector<int> v(5); // vector containing five elements.
This is just another syntax for calling your copy constructor (actually, for calling a constructor taking what is in the braces as parameters, in this case, your copy constructor).
Personally, I would say it's worse than before simply because it does the same... it just relies on C++ 11. So it adds dependencies without benefits. But your mileage may vary. You will have to ask your colleague.
I must admit that I'd never seen that before.
WikiPedia says this about C++11 initializer lists (search for "Uniform initialization"):
C++03 has a number of problems with initializing types. There are several ways to initialize types, and they do not all produce the same results when interchanged. The traditional constructor syntax, for example, can look like a function declaration, and steps must be taken to ensure that the compiler's most vexing parse rule will not mistake it for such. Only aggregates and POD types can be initialized with aggregate initializers (using SomeType var = {/stuff/};).
Then, later, they have this example,
BasicStruct var1{5, 3.2}; // C type struct, containing only POD
AltStruct var2{2, 4.3}; // C++ class, with constructors, not
// necessarily POD members
with the following explanation:
The initialization of var1 behaves exactly as though it were aggregate-initialization. That is, each data member of an object, in turn, will be copy-initialized with the corresponding value from the initializer-list. Implicit type conversion will be used where necessary. If no conversion exists, or only a narrowing conversion exists, the program is ill-formed. The initialization of var2 invokes the constructor.
They also have further examples for the case where initialiser list constructors were provided.
So based on the above alone: For the plain-old-data struct case, I don't know if there is any advantage. For a C++11 class, using the {} syntax may help avoid those pesky scenarios where the compiler thinks you're declaring a function. Maybe that is the advantage your colleague was referring to?
Sorry for comming late to this discussion but I want to add some points about the different types of initialization not mentioned by others.
Consider:
struct foo {
foo(int) {}
};
foo f() {
// Suppose we have either:
//return 1; // #1
//return {1}; // #2
//return foo(1); // #3
//return foo{1}; // #4
}
Then,
#1, #3 and #4 might call the copy/move constructor (if RVO isn't performed) whereas #2 won't call the copy/move constructor.
Notice that the most popular compilers do perform RVO and thus, in practice, all return statements above are equivalent. However, even when RVO is performed a copy/move constructor must be available (must be accessible to f and defined but not as deleted) for #1, #3 and #4 otherwise the compiler/linker will raise an error.
Suppose now that the constructor is explicit:
struct foo {
explicit foo(int) {}
};
Then,
#1 and #2 don't compile whereas #3 and #4 do compile.
Finally, if the constructor is explicit and no copy/move constructor is available:
struct foo {
explicit foo(int) {}
foo(const foo&) = delete;
};
none of the return statements compile/link.
This is known as list-initialization. The idea is that in C++11, you will have uniform initialization across the board, and avoid ambiguity where the compiler might think you may be making a function declaration (also known as a vexing parse). A small example:
vec3 GetValue()
{
return {x, y, z}; // normally vec(x, y, z)
}
One reason you would want to avoid list-initialization is where your class takes an initializer list constructor that does something different than you would expect.