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.
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.
Given:
struct X {
int m;
std::string s;
};
I can do:
X x; // invokes automatically defined default ctor
X y = { 5 }; // invokes whatever became of the original struct initialization but now maybe runs through C++ initializer-lists?
X z = { 5, "yolo" }; // I assume this is an initializer-list that is being handled by some rule for structs that either runs through a compiler created ctor or copy-from-initializer-list that is similarly compiler-created
and even...
std::vector<X> vx;
vx.push_back({ 99, "yo" }); // okay
But not...
vx.emplace_back(99, "yo"); // error VS 2017 v. 15.7.4
vx.emplace_back({99, "yo"}); // error VS 2017 v. 15.7.4
I'm not understanding the rules between initializer-lists, implicitly defined (or compiler defined) ctors, and forwarding functions like emplace_back()
Would someone be so kind as to either point me to the necessary bits of the standard or a good article on an in-depth discussion of what's become of all of the rules around structs and implicit construction and other compiler-supplied members such as copy / move operators?
I seem to be in need of a more comprehensive rules lesson - because it seems like emplace_back() ought to work for one of either emplace_back(int, std::string), or for emplace_back(initializer-list) - no?
X is an aggregate. While the specific definition of aggregate has changed in every standard, your type is an aggregate in all of them.
List-initialization for an aggregate does aggregate-initialization here. There's no constructor here - there's no "auto" constructor, no synthesized constructor. Aggregate-initialization does not create constructors or go through that mechanism. We're directly initializing each class member from the appropriate initializer in the braced-init-list. That's what both your y and your z are doing.
Now, for the second part. The relevant part of vector looks like:
template <typename T>
struct vector {
void push_back(T&&);
template <typename... Args>
void emplace_back(Args&&...);
};
A braced-init-list, like {99, "yo"}, does not have a type. And you cannot deduce a type for it. They can only be used in specific circumstances. push_back({99, "yo"}) works fine because push_back takes an X&& - it's not a function template - and we know how to do that initialization.
But emplace_back() is a function template - it needs to deduce Args... from the types of its arguments. But we don't have a type, there's nothing to deduce! There are some exceptions here (notably std::initializer_list<T> can be deduced), but here, we're stuck. You would have to write emplace_back(X{99, "yo"}) - which creates the X on the caller's side.
Similarly, emplace_back(99, "yo") doesn't work because emplace uses ()s to initialize, but you cannot ()-initialize an aggregate. It doesn't have a constructor!
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.
I would like to use the optional idiom inside my constexpr function to easily clarify if the variable is set or not.
What I have tried with std::experimental::optional:
constexpr bool call()
{
std::experimental::optional<bool> r;
r = true; // Error
// Similar error with:
// r = std::experimental::optional<bool>(true);
if (!r)
{
return false;
}
return *r;
}
I get the error: call to non-constexpr function - so the assignment is not possible, because this operation cannot be constexpr (Example).
But if I implement my own (very ugly, just for example) optional class, it works, because I don´t implement the assignment operator/constructor explicit.
template<typename T>
struct optional
{
bool m_Set;
T m_Data;
constexpr optional() :
m_Set(false), m_Data{}
{
}
constexpr optional(T p_Data) :
m_Set(true), m_Data(p_Data)
{
}
explicit constexpr operator bool()
{
return m_Set;
}
constexpr T operator *()
{
return m_Data;
}
};
How could I use std::..::optional in the same context with assignment inside constexpr functions?
Basically, you can't. The problem with your simple implementation is that it requires T be default-constructible - if this is not the case, this won't work.
To get around this, most implementation use either a union or some (suitably aligned) storage that can hold a T. If you are passed a T in the constructor, then all well and good, you can initialize this directly (hence it will be constexpr). However, the tradeoff here is that when calling operator=, copying the value across may require a placement-new call, which cannot be constexpr.
For example, from LLVM:
template <class _Up,
class = typename enable_if
<
is_same<typename remove_reference<_Up>::type, value_type>::value &&
is_constructible<value_type, _Up>::value &&
is_assignable<value_type&, _Up>::value
>::type
>
_LIBCPP_INLINE_VISIBILITY
optional&
operator=(_Up&& __v)
{
if (this->__engaged_)
this->__val_ = _VSTD::forward<_Up>(__v);
else
{
// Problem line is below - not engaged -> need to call
// placement new with the value passed in.
::new(_VSTD::addressof(this->__val_)) value_type(_VSTD::forward<_Up>(__v));
this->__engaged_ = true;
}
return *this;
}
As for why placement new is not constexpr, see here.
This is not possible, as explained in n3527:
Making optional a literal type
We propose that optional<T> be a literal type for trivially
destructible T's.
constexpr optional<int> oi{5};
static_assert(oi, ""); // ok
static_assert(oi != nullopt, ""); // ok
static_assert(oi == oi, ""); // ok
int array[*oi]; // ok: array of size 5
Making optional<T> a literal-type in general is impossible: the
destructor cannot be trivial because it has to execute an operation
that can be conceptually described as:
~optional() {
if (is_engaged()) destroy_contained_value();
}
It is still possible to make the destructor trivial for T's which
provide a trivial destructor themselves, and we know an efficient
implementation of such optional<T> with compile-time interface —
except for copy constructor and move constructor — is possible.
Therefore we propose that for trivially destructible T's all
optional<T>'s constructors, except for move and copy constructors,
as well as observer functions are constexpr. The sketch of reference
implementation is provided in this proposal.
In other words, it's not possible to assign a value to r even if you mark it as constexpr. You must initialize it in the same line.
How could I use std::..::optional in the same context with assignment
inside constexpr functions?
std::optional is meant to hold a value that may or may not be present. The problem with std::optional's assignment is that it must destroy the old state (call the destructor of the contained object) if any. And you cannot have a constexpr destructor.
Of cause, Trivial and integral types shouldn't have a problem, but I presume the generalization was to keep things sane. However, Assignment could have been made constexpr for trivial types. Hopefully, it will be corrected. Before then, you can role out yours. :-)
Even std::optional's constructor that you think is constexpr, is actually selectively constexpr (depending on whether the selected object constructor is). Its proposal can be found here
Unfortunately, constexpr support in std::optional is somewhat rudimentary; the constexpr-enabled member functions are just the (empty and engaged) constructors, the destructor and some observers, so you cannot alter the engaged state of an optional.
This is because there would be no way to implement assignment for non-trivially copyable types without using placement new and in-place destruction of the contained object, which is illegal within constexpr context. The same currently holds for copy and move constructors, although that may change with guaranteed copy elision, but in any case the standard marks those special member functions as non-constexpr, so you cannot use them in constexpr context.
The fix would be to make the assignment operator conditionally constexpr dependent on whether the contained type is trivial (std::is_trivial_v<T>).
There is some discussion of this issue at the reference implementation; although it's probably too late to get constexpr assignment for trivial optionals into the next version of the Standard, there's nothing preventing you writing your own (e.g. by copying and fixing the reference implementation).
Is there a rule of thumb to decide when to use the old syntax () instead of the new syntax {}?
To initialize a struct:
struct myclass
{
myclass(int px, int py) : x(px), y(py) {}
private:
int x, y;
};
...
myclass object{0, 0};
Now in the case of a vector for example, it has many constructors. Whenever I do the following:
vector<double> numbers{10};
I get a vector of 1 element instead of one with 10 elements as one of the constructors is:
explicit vector ( size_type n, const T& value= T(), const Allocator& = Allocator() );
My suspicion is that whenever a class defines an initializer list constructor as in the case of a vector, it gets called with the {} syntax.
So, is what I am thinking correct. i.e. Should I revert to the old syntax only whenever a class defines an initializer list constructor to call a different constructor? e.g. to correct the above code:
vector<double> numbers(10); // 10 elements instead of just one element with value=10
I've found the answer in the standard docs(latest draft). Hopefully, I'll try to explain what I understood.
First, if a class defines an initialization list constructor, then it is used whenever suitable:
§ 8.5.4 (page 203)
Initializer-list constructors are
favored over other constructors in
list-initialization (13.3.1.7).
I think this is a great feature to have, eliminating the headache associated with the non-uniform style :)
Anyway, the only gotcha(which my question is about) is that if you design a class without the initializer constructor, then you add it later you may get surprising result.
Basically, imagine std::vector didn't have the initializer list constructor, then the following would create a vector with 10 elements:
std::vector<int> numbers{10};
By adding the initializer list constructor, the compiler would favor it over the other constructor because of the {} syntax. This behavior would happen because the elements of the init-list {10} are accepted using the init-list constructor. If there is no acceptable conversion, any other constructor shall be used e.g.:
std::vector<string> vec{10};
// a vector of 10 elements.
// the usual constructor got used because "{0}"
// is not accepted as an init-list of type string.
Take a look at this:
http://www.informit.com/guides/content.aspx?g=cplusplus&seqNum=453&rll=1
The use of a {}-style initializers on a variable has no direct mapping to the initialization lists on any constructors of the class. Those constructor initialization lists can be added/removed/modified without breaking existing callers.
Basically the different behavior of the container is special, and requires special code in that container, specifically a constructor taking a std::initializer_list. For POD and simple objects, you can use {} and () interchangeably.