direct-initialization vs direct-list-initialization (C++) - c++

DIRECT- VS COPY-INITIALIZATION
Through this question (Is it direct-initialization or copy-initialization?) I learned the differences between direct-initialization and copy-initialization:
direct-initialization copy-initialization
----------------------- ---------------------
obj s("value"); obj s = obj("value");
obj s = "value";
obj s{"value"}; obj s = {"value"};
obj s = obj{"value"};
I mention it here for the sake of completeness. My actual questions for this page are listed in the next paragraph >>
DIRECT-INITIALIZATION VS DIRECT-LIST-INITIALIZATION
The answers revealed that within the category of direct-initialization, one can make a difference between direct-initialization and direct-list-initialization.:
obj s("value"); // direct-initialization
obj s{"value"}; // direct-list-initialization
I know that list-initialization doesn't allow narrowing, such that an initialization like int x{3.5}; won't compile. But besides this, I got a couple of questions:
(1) Is there any difference in compiler output between
obj s("value"); and obj s{"value"};?
Let's consider a compiler without any optimizations. I would like to know any possible technical difference :-)
(2) Perhaps I should ask the exact same question for a multi-variable initialization, like:
obj s("val1", "val2"); and obj s{"val1", "val2"};
(3) I have noticed that the list-initialization can sometimes call a different constructor, like in:
vector<int> a{10,20}; //Curly braces -> fills the vector with the arguments
vector<int> b(10,20); //Parentesis -> uses arguments to parameterize some functionality
How is that possible?
DID WE COVER ALL POSSIBLE INITIALIZATIONS HERE?
From my limited knowledge on C++, I believe that all possible initializations of objects (either native-typed or user-defined-typed objects) have been covered in the examples above. Is that correct? Did I overlook something?
PS: I am learning C++ (I do know C, but not yet C++), so please don't be too hard on me ;-)

(1) Is there any difference in compiler output between
obj s("value"); and obj s{"value"};? Let's consider a compiler
without any optimizations. I would like to know any possible technical
difference :-)
(2) Perhaps I should ask the exact same question for a
multi-variable initialization, like: obj s("val1", "val2"); and
obj s{"val1", "val2"};
(3) I have noticed that the list-initialization can
sometimes call a different constructor, like in:
vector<int> a{10,20}; //Curly braces -> fills the vector with the arguments
vector<int> b(10,20); //Parentesis -> uses arguments to parameterize some functionality
How is that possible?
If there is an initializer-list constructor for the class type obj, it will always be preferred over other other constructors for brace-init-initializers (list initialization), in your case obj s{"value"};;
What this means is that if you have a constructor that takes std::initializer_list<T> as its first parameter and other parameters are defaulted, then it is preferred. Example
struct A{
A(std::initializer_list<std::string>); //Always be preferred for A a{"value"}
A(std::string);
};
std::vector<T> and other STL containers have such initializer-list constructors.
Otherwise, Overload resolution kicks in and it falls back to any available constructor as selected by the overload resolution process;
Otherwise, if the class has no user defined constructors and it's an aggregate type, it initializes the class members directly.
DID WE COVER ALL POSSIBLE INITIALIZATIONS HERE? From my
limited knowledge on C++, I believe that all possible initializations
of objects (either native-typed or user-defined-typed objects) have
been covered in the examples above. Is that correct? Did I overlook
something?
Nope. you didn't. Excluding Reference initialization, there are five ways objects may be initialized in C++.
Direct Initialization
List Initialization
Copy Initialization
Value Initialization
Aggregate Initialization (only for aggregate types)
You can find more information here

List-initialization guarantee left-to-right order of arguments evaluation. In this example we will create std::tuple from istream data and then output tuple example can be found here:
#include <iostream>
#include <sstream>
#include <tuple>
template<typename T, typename CharT>
T extract(std::basic_istream<CharT>& is) {
T val;
is >> val;
return val;
}
void print(const std::tuple<int, long, double>& t) {
using std::cout;
cout << std::get<0>(t) << " " << std::get<1>(t) << " " << std::get<2>(t) << std::endl;
}
int main()
{
std::stringstream ss1;
std::stringstream ss2;
ss1 << 1 << " " << 2 << " " << 3;
ss2 << 1 << " " << 2 << " " << 3;
auto compilerOrder = std::tuple<int, long, double>( extract<int>(ss1), extract<long>(ss1), extract<double>(ss1) );
auto leftToRightOrder = std::tuple<int, long, double>{ extract<int>(ss2), extract<long>(ss2), extract<double>(ss2) };
print(compilerOrder);
print(leftToRightOrder);
}
Output:
3 2 1
1 2 3
As you can see, the difference will be seen then we use multiple times same stream-like resource inside function brackets.
Also my question about that

Related

Why are many curly brackets treated differently by C++ compilers?

In the following C++20 program I put by mistake one extra pair of curved braces {} in B{{{{A{}}}}}:
#include <iostream>
struct A
{
A() { std::cout << "A() "; }
A( A&& ) = delete;
~A() { std::cout << "~A() "; }
};
struct B { std::initializer_list<A> l; };
int main()
{
[[maybe_unused]] auto x = B{{{{A{}}}}};
std::cout << ". ";
}
Clang rejected it, however with a strange error:
error: call to deleted constructor of 'const A'
But to my surprise GCC accepted it( https://gcc.godbolt.org/z/aPWe13xfc ).
Could you please explain why GCC accepts it (how does it treat extra curved braces)?
B{…}, since the single element of the initializer list is not designated and is not of type B (as it has no type at all), is aggregate initialization ([dcl.init.list]/3.4). B::l is thus copy-initialized from {{{A{}}}}; it's a specialization of std::initializer_list, so /3.6 and /5 apply. An "array of 1 const A" is created, and {{A{}}} is the initializer for its single element.
We can thus reduce the code to
const A a = {{A{}}};
with no mention of B at all, and indeed Clang and GCC produce the same disagreement for this line. Clang appears to be correct to reject it: that initialization is sent to constructors by /3.7, and obviously there is no constructor that could be viable (thus the error about the deleted move constructor).
Oddly, removing the extra pair of braces here (or in the original) causes both compilers to accept:
const A a = {A{}};
despite the fact that A is not an aggregate and so /3.7 still applies. Presumably both compilers are overenthusastically performing "guaranteed copy elision" (albeit to different degrees), identifying the prvalue A{} with an object eventually to be initialized by it; that, however, takes place only per [dcl.init.general]/16.6.1, which never comes into play in this analysis.

Which type trait would indicate that type is memcpy assignable? (tuple, pair)

I would like to know what type introspection I can do to detect types that assignable by simply raw memory copy?
For example, as far I understand, built-in types tuples of built-in types and tuple of such tuples, would fall in this category.
The motivation is that I want to transport raw bytes if possible.
T t1(...); // not necessarely default constructible
T t2(...);
t1 = t2; // should be equivalent to std::memcpy(&t1, &t2, sizeof(T));
// t1 is now an (independent) copy of the value of t2, for example each can go out of scope independently
What type_trait or combination of type_traits could tell at compile time if assignment can be (in principle) replaced by memcpy?
I tried what would work for the types I would guess should fullfil this condition and to my surprise the only one that fit the behavior is not std::is_trivially_assignable but std::trivially_destructible.
It makes sense to some level, but I am confused why some other options do not work with the expected cases.
I understand that there may not be a bullet proof method because one can always write a class that effectively is memcopyable, that cannot be "detected" as memcopyable, but I am looking for one that works for the simple intuitive cases.
#include<type_traits>
template<class T> using trait =
std::is_trivially_destructible
// std::is_trivial
// std::is_trivially_copy_assignable
// std::is_trivially_copyable // // std::tuple<double, double> is not trivially copyable!!!
// std::is_trivially_default_constructible
// std::is_trivially_default_constructible
// std::is_trivially_constructible
// std::is_pod // std::tuple<double, double> is not pod!!!
// std::is_standard_layout
// std::is_aggregate
// std::has_unique_object_representations
<T>
;
int main(){
static_assert((trait<double>{}), "");
static_assert((trait<std::tuple<double, double>>{}), "");
static_assert((not trait<std::tuple<double, std::vector<double>>>{}), "");
static_assert((not trait<std::vector<double>>{}), "");
}
Of course my conviction that tuple should be memcopyable is not based on the standard but based on common sense and practice. That is, because this is generally ok:
std::tuple<double, std::tuple<char, int> > t1 = {5.1, {'c', 8}};
std::tuple<double, std::tuple<char, int> > t2;
t2 = t1;
std::tuple<double, std::tuple<char, int> > t3;
std::memcpy(&t3, &t1, sizeof(t1));
assert(t3 == t2);
As a proof of principle, I implemented this. I added a couple of conditions related to the size to avoid some possible misleading specialization of std::tuple.
template<class T>
struct is_memcopyable
: std::integral_constant<bool, std::is_trivially_copyable<T>{}>{};
template<class T, class... Ts>
struct is_memcopyable<std::tuple<T, Ts...>> :
std::integral_constant<bool,
is_memcopyable<T>{} and is_memcopyable<std::tuple<Ts...>>{}
>
{};
template<class T1, class T2>
struct is_memcopyable<std::pair<T1, T2>> :
std::integral_constant<bool,
is_memcopyable<T1>{} and is_memcopyable<T2>{}
>
{};
This is a very limited workaround because a class like:
struct A{ std::tuple<double, double> t; };
will still unfortunately be reported as non trivially copyable and non memcopyable.
The correct test is in fact std::is_trivially_copyable, which allows use of memcpy for both making a new object and modifying an existing one.
Although you may be surprised that these return false for types where your intuition tells you that memcpy ought to be ok, they are not lying; the Standard indeed makes memcpy undefined behavior in these cases.
In the particular case of std::pair, we can get some insight into what goes wrong:
int main()
{
typedef std::pair<double,double> P;
std::cout << "\nTC: " << std::is_trivially_copyable<P>::value;
std::cout << "\nTCC: " << std::is_trivially_copy_constructible<P>::value;
std::cout << "\nTCv: " << std::is_trivially_constructible<P, const P&>::value;
std::cout << "\n CC: " << std::is_copy_constructible<P>::value;
std::cout << "\n MC: " << std::is_move_constructible<P>::value;
std::cout << "\nTCA: " << std::is_trivially_copy_assignable<P>::value;
std::cout << "\nTCvA:" << std::is_trivially_assignable<P, const P&>::value;
std::cout << "\n CA: " << std::is_copy_assignable<P>::value;
std::cout << "\n MA: " << std::is_move_assignable<P>::value;
std::cout << "\nTD: " << std::is_trivially_destructible<P>::value;
}
TC: 0
TCC: 1
TCv: 1
CC: 1
MC: 1
TCA: 0
TCvA:0
CA: 1
MA: 1
TD: 1
Evidently it isn't trivially copy assignable.1
The pair assignment operator is user-defined, so not trivial.
1I think that clang, gcc, and msvc are all wrong here, actually, but if it satisfied std::_is_trivially_copy_assignable it wouldn't help, because TriviallyCopyable requires that the copy constructor, if not deleted, is trivial, and not the TriviallyCopyAssignable trait. Yeah, they're different.
A copy/move assignment operator for class X is trivial if it is not user-provided and...
vs
is_assignable_v<T, const T&> is true and the assignment, as defined by
is_assignable, is known to call no operation that is not trivial.
The operations called by pair<double, double>'s copy assignment operator are the assignments of individual doubles, which are trivial.
Unfortunately, the definition of trivially copyable relies on the first, which pair fails.
A trivially copyable class is a class:
where each copy constructor, move constructor, copy assignment operator, and move assignment operator is either deleted or trivial,
that has at least one non-deleted copy constructor, move constructor, copy assignment operator, or move assignment operator, and
that has a trivial, non-deleted destructor.
This is only a partial answer to your question:
Type traits don't necessarily mean what their name says literally.
Specifically, let's take std::is_trivially_copyable. You were - rightly - surprised that a tuple of two double's is not trivially copyable. How could that be?!
Well, the trait definition says:
If T is a TriviallyCopyable type, provides the member constant value equal true. For any other type, value is false.
and the TriviallyCopyable concept has the following requirement in its definition:
Every copy constructor is trivial or deleted
Every move constructor is trivial or deleted
Every copy assignment operator is trivial or deleted
Every move assignment operator is trivial or deleted
At least one copy constructor, move constructor, copy assignment operator, or move assignment operator is non-deleted
Trivial non-deleted destructor
Not quite what you would expect, right?
With all in mind, it's not necessarily the case that any of the standard library traits would combine to fit the exact requirements of "constructible by memcpy()'ing".
To try and answer your question: std::memcpy() does not have any direct requirements but it does have these stipulations:
Copies count bytes from the object pointed to by src to the object pointed to by dest. Both objects are reinterpreted as arrays of unsigned char.
If the objects overlap, the behavior is undefined.
If either dest or src is a null pointer, the behavior is undefined, even if count is zero.
If the objects are not TriviallyCopyable, the behavior of memcpy is not specified and may be undefined.
Now to have the qualifications that an object is Trivially Copyable the following conditions or requirements must be met:
Every copy constructor is trivial or deleted
Every move constructor is trivial or deleted
Every copy assignment operator is trivial or deleted
Every move assignment operator is trivial or deleted
at least one copy constructor, move constructor, copy assignment operator, or move assignment operator is non-deleted
Trivial non-deleted destructor
This implies that the class has no virtual functions or virtual base classes.
Scalar types and arrays of TriviallyCopyable objects are TriviallyCopyable as well, as well as the const-qualified (but not volatile-qualified) versions of such types.
Which leads us to std::is_trivially_copyable
If T is a TriviallyCopyable type, provides the member constant value equal true. For any other type, value is false.
The only trivially copyable types are scalar types, trivially copyable classes, and arrays of such types/classes (possibly const-qualified, but not volatile-qualified).
The behavior is undefined if std::remove_all_extents_t is an incomplete type and not (possibly cv-qualified) void.
with this nice feature since c++17:
Helper variable template
template< class T >
inline constexpr bool is_trivially_copyable_v = is_trivially_copyable<T>::value;
And you would like to try and use a type_trait to use std::tuple<> with std::memcpy().
But we need to ask ourselves if std::tuple is Trivially Copyable and why?
We can see the answer to that here: Stack-Q/A: std::tuple Trivially Copyable? and according to that answer; it is not because the standard does not require the copy/move assignment operators to be trivial.
So the answer that I would think that is valid would be this: No std::tuple is not Trivially Copyable but std::memcpy() doesn't require it to be but only states that if it isn't; it is UB. So can you use std::tuple with std::memcpy? I think so, but is it safe? That can vary and can produce UB.
So what can we do from here? Take a risk? Maybe. I found something else that is related but have not found anything out about it regarding if it is Trivially Copyable. It is not a type_trait, but it is something that might be able to be used in conjunction with std::tuple & std::memcpy and that is std::tuple_element. You might be able to use this to do the memcpy, but I'm not fully sure about it. I have searched to find out more about std::tuple_element to see if it is Trivially Copyable but haven't found much so all I can do is a test to see what Visual Studio 2017 says:
template<class... Args>
struct type_list {
template<std::size_t N>
using type = typename std::tuple_element<N, std::tuple<Args...>>::type;
};
int main() {
std::cout << std::boolalpha;
std::cout << std::is_trivially_copyable<type_list<int, float, float>>::value << '\n';
std::cout << std::is_trivially_copyable<std::tuple<int, float, float>>::value << '\n';
_getch(); // used to stop visual studio debugger from closing.
return 0;
}
Output:
true
false
So it appears if we wrap std::tuple_element in a struct it is Trivially Copyable. Now the question is how do you integrate this with your std::tuple data sets to use them with std::memcpy() to be type safe. Not sure if we can since std::tuple_element will return the types of the elements within a tuple.
If we even tried to wrap a tuple in a struct as such:
template<class... Args>
struct wrapper {
std::tuple<Args...> t;
};
And we can check it by:
{
std::cout << std::is_trivially_copyable< wrapper<int, float, float> >::value << std::endl;
}
It is still false. However we have seen were std::tuple was already used in the first struct and the struct returned true. This may be of some help to you, to ensure you can safely use std::memcpy, but I can not guarantee it. It is just that the compiler seems to agree with it. So this might be the closest thing to a type_trait that might work.
NOTE: - All the references about memcpy, Trivially Copyable concepts, is_trivially_copyable, std::tuple & std::tuple_element were taken from cppreference and their relevant pages.

DR 2137 is not clear to me

In DR 2137 we have the following text (emphasis is mine):
It is not clear in code like the following that selecting a copy/move
constructor is the correct choice when an initializer list contains a
single element of the type being initialized, as required by issue
1467:
#include <initializer_list>
#include <iostream>
struct Q {
Q() { std::cout << "default\n"; }
Q(Q const&) { std::cout << "copy\n"; }
Q(Q&&) { std::cout << "move\n"; }
Q(std::initializer_list<Q>) { std::cout << "initializer list\n"; }
};
int main() {
Q x = Q { Q() };
}
Here the intent is that Q objects can contain other Q objects, but
this is broken by the resolution of issue 1467.
I'd like to understand why the code above would be broken by the resolution of issue 1467.
DR 2147 has this statement: "the intent is that Q objects can contain other Q objects". Given that, it assumes that if the user constructs a Q from a braced-init-list containing other Qs, that the intent of the user is to call the initializer_list constructor.
Given that assumption, it is therefore "broken" to not call the initializer_list constructor, which 1467 would cause.
Whether you agree with this logic or not, that's the thinking behind 2147. It's also the thinking that permits [over.match.list] to prioritize initializer_list constructors over any other constructor type. So in that way, it is consistent.

Different ways of calling an initializer-list-constructor

Consider this example for initializer-list-constructor usage:
std::vector<std::string> v = { "xyzzy", "plugh", "abracadabra" };
std::vector<std::string> v({ "xyzzy", "plugh", "abracadabra" });
std::vector<std::string> v{ "xyzzy", "plugh", "abracadabra" };
Are there any differences (even slightly) between them?
In a large project, where you have to define a standard, which style would you choose?
I would prefer the first style, the third can be easly confused with a call to a constructor with args. Also the first style looks familiar to other programming languages.
In case of a vector of strings, there's no difference between the three forms. There can, however, be a difference between the first and the other two if the constructor taking the initializer_list is explicit. In that case, the first, which is copy-list-initialization, is not allowed, while the other two, which are direct-list-initialization, are allowed.
Because of that reason, my preference would be the third form. I'd avoid the second because the parentheses are redundant.
Further differences arise, as Yakk points out in the comments, when the type being constructed does not have a constructor taking an initializer_list.
Say for instance, the type being constructed has a constructor that takes 3 arguments, all of type char const *, instead of the initializer_list constructor. In that case, forms 1 & 3 are valid, but 2 is ill-formed, because the braced-init-list cannot match the 3 argument constructor when enclosed in parentheses.
If the type does have an initializer list constructor, but the elements of the braced-init-list are not implicitly convertible to initializer_list<T>, then other constructors will be considered. Assuming another constructor that is a match exists, form 2 results in an intermediate copy being constructed, while the other two don't. This can be demonstrated by the following example, compiled with -fno-elide-constructors.
struct foo
{
foo(int, int) { std::cout << __PRETTY_FUNCTION__ << std::endl; }
foo(foo const&) { std::cout << __PRETTY_FUNCTION__ << std::endl; }
foo(std::initializer_list<std::string>) { std::cout << __PRETTY_FUNCTION__ << std::endl; }
};
int main()
{
foo f1 = {1,2};
std::cout << "----\n";
foo f2({1,2});
std::cout << "----\n";
foo f3{1,2};
}
Output:
foo::foo(int, int)
----
foo::foo(int, int)
foo::foo(const foo&)
----
foo::foo(int, int)
The following case is not part of the question, but still good to be aware of. Using nested braces can result in unintuitive behavior in certain cases. Consider
std::vector<std::string> v1{{ "xyzzy", "plugh", "abracadabra" }};
std::vector<std::string> v2{{ "xyzzy", "plugh"}};
v1 works as expected and will be a vector containing 3 strings, while v2 results in undefined behavior. Refer to this answer for a detailed explanation.

C++ about generic initialization in templates

I am writing a generic function like below.
template<class Iterator, class T>
void foo(Iterator first, Iterator last) {
T a;
cout << a << endl;
// do something with iterators
}
typedef vector<double>::iterator DblPtr;
vector<double> values;
foo< DblPtr, int>();
This functions prints out an undefined value for variable a, while if I change the initialization into
///
T a = T()
cout << a << endl;
// do something with iterators
I can see that the initialized value is 0 as I am expecting.
If I call T a the variable is initialized with the default value, but if i call T a = T() I believe that due to optimization the copy constructor should be called with the value of T() that is still the default one.
I cannot understand what is the difference behind these 2 lines and the reason why this happens?
First of all, default initiaization of built-in types such as int leaves them uninitialized. Value initialization leaves them zero-initialized. As for your example
This is a default initialization:
T a;
This is a value initialization, using copy initialization:
T a = T();
You are right that copies can be elided here, so this has the effect of creating a single value-initialized T object. However, it does require that T be copyable or move-copyable. This is the case with built-in types, but it is a restriction to bear in mind.
The copy initialization syntax is required because this is a function declaration:
T a();
but C++11 allows you to value-initialize like this:
T a{};