Is storing initializer_lists undefined behaviour? [duplicate] - c++

This question already has answers here:
C++: Life span of temporary arguments?
(4 answers)
Closed 7 months ago.
This question is a follow up to How come std::initializer_list is allowed to not specify size AND be stack allocated at the same time?
The short answer was that calling a function with brace-enclosed list foo({2, 3, 4, 5, 6}); conceptually creates a temporary array in the stackspace before the call and then passes the initializer list which (like string_view) just references this local temporary array (probably in registers):
int __tmp_arr[5] {2, 3, 4, 5, 6};
foo(std::initializer_list{arr, arr + 5});
Now consider the following case where I have nested initializer_lists of an object "ref". This ref object stores primitives types or an initializer_list recursively in a variant. My question now is: Is this undefined behaviour? It appears to work with my code, but does it hold up to the standard? My reason for doubting is that when the inner constructor calls for the nested brace-enclosed lists return, the temporary array which the initializer list is refering to could be invalidated because the stack pointer is reset (thus saving the initializer_list in the variant preserves an invalid object). Writing to subsequent memory would then overwrite values refered to by the initializer list. Am I wrong in believing that?
CompilerExplorer
#include <variant>
#include <string_view>
#include <type_traits>
#include <cstdio>
using val = std::variant<std::monostate, int, bool, std::string_view, std::initializer_list<struct ref>>;
struct ref
{
ref(bool);
ref(int);
ref(const char*);
ref(std::initializer_list<ref>);
val value_;
};
struct container
{
container(std::initializer_list<ref> init) {
printf("---------------------\n");
print_list(init);
}
void print_list(std::initializer_list<ref> list)
{
for (const ref& r : list) {
if (std::holds_alternative<std::monostate>(r.value_)) {
printf("int\n");
} else if (std::holds_alternative<int>(r.value_)) {
printf("int\n");
} else if (std::holds_alternative<bool>(r.value_)) {
printf("bool\n");
} else if (std::holds_alternative<std::string_view>(r.value_)) {
printf("string_view\n");
} else if (std::holds_alternative<std::initializer_list<ref>>(r.value_)) {
printf("initializer_list:\n");
print_list(std::get<std::initializer_list<ref>>(r.value_));
}
}
}
};
ref::ref(int init) : value_{init} { printf("%d stored\n", init); }
ref::ref(bool init) : value_{init} { printf("%s stored\n", init ? "true" : "false"); }
ref::ref(const char* init) : value_{std::string_view{init}} { printf("%s stored\n", init); }
ref::ref(std::initializer_list<ref> init) : value_{init} { printf("initializer_list stored\n", init); }
int main()
{
container some_container = { 1, true, 5, { {"itemA", 2}, {"itemB", true}}};
}
Output:
1 stored
true stored
5 stored
itemA stored
2 stored
initializer_list stored
itemB stored
true stored
initializer_list stored
initializer_list stored
---------------------
int
bool
int
initializer_list:
initializer_list:
string_view
int
initializer_list:
string_view
bool

First of all, copying a std::initializer_list does not copy the underlying objects. (cppreference)
and
container some_container = { 1, true, 5, { {"itemA", 2}, {"itemB", true}}};
actually compiles to something like
container some_container = { // initializer_list<ref>
ref{1},
ref{true},
rer{5},
ref{ // initializer_list<ref>
ref{ // initializer_list<ref>
ref{"itemA"},
ref{2}
},
ref{ // initializer_list<ref>
ref{"itemB"},
ref{true}
}
}
};
and all those object's lifetime* end at end of full expression (the ; here)
*including all initializer_list<ref>, their underlying array, and all the belonging ref objects, but not the one in the constructor (copy elision may apply though)
so
yes it's fine to use those object in the constructor.
those object are gone after the ; so you should not use the stored object (in initializer_list) anymore
godbolt example with noisy destructor and action after the construction

Related

strange std::list iteration behavior

#include <list>
#include <iostream>
#include <utility>
#include <conio.h>
std::list<int>&& asd()
{
std::list<int> a{ 4, 5, 6 };
return std::move( a );
}
int main()
{
std::list<int> a{ 1, 2, 3 };
std::list<int> &&b = std::move( a );
for( auto &iter : b )
{ std::cout<<iter<<' '; }
std::list<int> &&c{ asd() };
for( auto &iter : c )
{ std::cout<<iter<<' '; _getch(); }
}
this is what my program outputs:
1 2 3
4 3502376 454695192 3473720 4 3502376 454695192 3473720 4 3502376 454695192 3473720 4
std::list<int> a{ 4, 5, 6 };
This object is declared locally inside this function, so it gets destroyed when the function returns.
std::list<int>&& asd()
This function returns an rvalue reference. A reference to some unspecified object.
What happens here is that this "unspecified object" gets destroyed when this function returns, so the caller ends up holding a reference to a destroyed object.
return std::move( a );
The dirty little secret is that std::move() does not actually move anything. It never did, it never will. All that std::move() does is cast its parameter into an rvalue reference. Whether the object gets moved, or not, requires other dominoes to fall into place. std::move() merely provides one piece of the puzzle, but all other pieces of this puzzle must exist in order for an actual move to happen.
Here, those other pieces of the puzzle do not exist. Therefore, nothing gets moved. A reference to a destroyed object gets returned, and all subsequent use of the destroyed object results in undefined behavior.

Different values of same variable in class

#include<iostream>
using namespace std;
template<class T>
class vee
{
T* v;
int size;
public:
vee(int m)
{
v = new T[size=m];
cout<<size<<"\n";
}
vee(T* a)
{
cout<<size<<"\n";
for(int i=0;i<size;i++)
{
v[i]=a[i];
}
}
};
int main()
{
int x[]={1,2,3};
int y[]={2,3,4};
vee<int> v1(3);
v1=x;
return 0;
}
Why i am getting 2 different values of "size" ?
I have created a constructor to intilize the parameter size and it shows correct value in first constructor but it throws a garbage value in the second constructor ,why??
Why i am getting 2 different values of "size" ?
vee(T* a) is called converting constructor. When you write something like v1=x;, array x decays to a pointer, then it is converted to vee with provided converting constructor.
v1=x; is as if you wrote v1=vee<int>(x);
As you can see a temporary instance is created with undefined size and pointer, which is bad. Then you assign this instance to v1 which is worse.
If you do not want this autoconversion in the future, declare your constructor explicit
explicit vee(T* a)
When doing v1=x, you actually go and create new object, since you have not overrided the '=' operator.
but it throws a garbage value in the second constructor ,why??
vee(int m)
{
v = new T[size=m]; // in this constructor you set size
cout<<size<<"\n";
}
vee(T* a)
{
// but in this constructor you don't set size
cout<<size<<"\n";
for(int i=0;i<size;i++)
{
v[i]=a[i];
}
}
So, when you create a vee using the second constructor - and read from size - your program has undefined behaviour.

Make a C++ initializer automatically detect union member?

OK, the standard still says that brace-initializing a union initializes only the first member. I suppose this puts this question more in the "Wouldn't it be nice if...?" category.
Does it have to be this way? After all, we now have automatic type detection for other types.
auto x = 3; // int
auto s = "foo"; // char *
auto w = L"Foo"; // wchar_t *
So why not with a union? Given:
struct init
{
int t;
union {
long long x;
char *s;
wchar_t *w;
};
};
Currently you can only brace-initialize init::x (with an int), not s or w.
Automatic type detection might be extended so that the union member initialized is selected based on the type of the initializer:
auto init initial_data [] = {
{ 0, 3 }, // initializes x, brace elision
{ 1, "foo" }, // initializes s
{ 2, L"Foo" } // initializes w
};
(of course, this should not compile in the current standard.)
This would make it possible to put all of the initial_data in a single initializer list. (I suspect the auto keyword would have to go somewhere else).
Is there something making this a Bad Idea (beyond "nobody's thought of it yet" or "it's too hard to implement")? Currently you have to do something horrendous like:
#define RI(X) reinterpret_cast<long long>(X)
const init initial_data[] = {
{ 0, 3 }, // initializes x, brace elision
{ 1, RI("foo") }, // initializes s
{ 2, RI(L"Foo") } // initializes w
};
I wouldn't want to go through these contortions, except that a std::exception must be initialized with a std::string (i.e. based on char), so I can't just have an initializer list with all wchar_t*. And I'd really like all of those initializers in the same place.
Update
In my introduction I mentioned:
(though A union can have member functions (including constructors and destructors)
I just realized this enables you to write the required constructors yourself!
struct init {
init(long long v) : t(type::longlong), x(v) {}
init(char const* v) : t(type::pcsz), s(v) {}
init(wchar_t const* v) : t(type::pwcsz), w(v) {}
enum class type { longlong, pcsz, pwcsz } t;
union { long long x; char const* s; wchar_t const* w; };
};
Now this compiles:
init initial_data [] = {
3 , // initializes x, brace elision
"foo" , // initializes s
L"Foo" // initializes w
};
See it Live On Coliru
Q. Is there something making this a Bad Idea (beyond "nobody's thought of it yet" or "it's too hard to implement")
In general union are very often a bad idea. It's hard to use them safely. In this respect they're a bit "second class" citizens in C++ (though A union can have member functions (including constructors and destructors), but not virtual functions, these days).
Letting the compiler select "the" slot to receive the initializer is likely to run into ambiguities very soon.
With brace initialization, they could add some restrictions (no narrowing/widening conversions) like there already are with initializer lists in C++11.
But this is all fantasy. The short of it is: this is not a language feature.
boost::variant has this behaviour.
Notes
the which() member is basically your type discriminator (t)
you can optimize for size: BOOST_VARIANT_MINIMIZE_SIZE
you should make the char/wchar_t pointers const* if you want to assign from a literal!
Live On Coliru
#define BOOST_VARIANT_MINIMIZE_SIZE
#include <boost/variant.hpp>
using init = boost::variant<long long, char const*, wchar_t const*>;
#include <iostream>
int main() {
init initial_data [] = {
3 , // initializes x, brace elision
"foo" , // initializes s
L"Foo" // initializes w
};
for(auto& v : initial_data)
std::cout << v.which() << "\t" << v << "\n";
}
Output:
0 3
1 foo
2 0x401f14

Error inserting custom class to map as value

I'm having trouble understanding why this isn't working as I expect it to. It may be that I'm using Visual Studio 2013, but hey.
This code is part of the item randomization system in a game engine I'm writing.
// the chance a rarity will have a given number of affixes
std::unordered_map<ItemRarities, ChanceSelector<int>> affixCountChances = {
std::pair<ItemRarities, ChanceSelector<int>>(ItemRarities::Cracked,
{ ChanceSelector<int>(
{ ChancePair(int, 100, 0) }) }),
std::pair<ItemRarities, ChanceSelector<int>>(ItemRarities::Normal,
{ ChanceSelector<int>(
{ ChancePair(int, 80, 0),
ChancePair(int, 20, 1) }) }),
// snip for conciseness (there are 3 more)
};
And this is the ChanceSelector class:
using Percentage = int;
#define ChancePair(T, p, v) std::pair<Percentage, T>(p, v)
template <class T>
class ChanceSelector
{
private:
std::unordered_map<T, Percentage> _stuff;
public:
ChanceSelector()
{
}
~ChanceSelector()
{
if (_stuff.size() > 0)
_stuff.clear();
}
ChanceSelector(std::initializer_list<std::pair<Percentage, T>> list)
{
// snip for conciseness
}
T Choose()
{
// snip for conciseness
}
};
The above code compiles fine but I have two questions:
I don't understand why using ChanceSelector in std::pair requires a default constructor. Explicitly, it looks like I'm calling the constructor with the initializer list.
When the applications runs, it crashes with: Unhandled exception at 0x01762fec in (my
executable): 0xC0000005: Access violation reading location 0xfeeefeee.
Number 2 goes away if I only have one item in that map or if I change the definition of affixCountChances to std::unordered_map<ItemRarities, ChanceSelector<int>*> (and adjust the rest accordingly). The error dumps me at this code in list:
for (_Nodeptr _Pnext; _Pnode != this->_Myhead; _Pnode = _Pnext)
{
_Pnext = this->_Nextnode(_Pnode); // <-- this line
this->_Freenode(_Pnode);
}
Further inspection reveals the error happened in the destructor. _stuff is empty:
~ChanceSelector()
{
if (_stuff.size() > 0)
_stuff.clear();
}
It is legitly calling the destructor. Items are being removed from _stuff but I don't see why it would be calling the destructor. The crash happens after all items have been constructed and affixCountChances contains all items. I would assume that means it's destroying all the temporaries it created but I don't see why it would be creating temporaries.
Edit:
Constructor of ChanceSelector:
ChanceSelector(std::initializer_list<std::pair<Percentage, T>> list)
{
int total = 0;
int last = 100;
for (auto& item : list)
{
last = item.first;
total += item.first;
_stuff[item.second] = total;
}
// total must equal 100 so that Choose always picks something
assert(total == 100);
}
To answer your two questions:
std::pair requires a default constructor, because you can do something like
std::pair<int, MyClass> myPair();
which creates a copy of your class using the default constructor (The values of the pair are actual values and not references):
// MSVC implementation
template<class _Ty1,class _Ty2>
struct pair
{ // store a pair of values
typedef pair<_Ty1, _Ty2> _Myt;
typedef _Ty1 first_type;
typedef _Ty2 second_type;
pair()
: first(), second() // Here your class gets default constructed
{ // default construct
}
// .....
_Ty1 first; // the first stored value
_Ty2 second; // the second stored value
};
The template of the pair gets fully implemented, so you need a default constructor event if you are not using the line above.
One way to avoid this dependency is to use pointers in the std::pair, this then sets the default value of the second value of the pair to nullptr:
std::pair<int, MyClass*> myPair();
0xFEEEFEEE indicates that the storage, where your pointer itself was stored has already been deleted (e.g. working on a deleted class reference).
This deletion seems to occur somewhere outside the code you have posted here.
For more Magic Numbers see Magic Numbers on Wikipedia
Edit:
Additionally, the contents of the initializer list do not exist after the constructor call. You might have there a reference copied instead of the actual object, which then gets deleted. The msvc implementation of the std::unordered_map uses a std::list as base for storing items. I'm not able to give your more information about this with the given code.
Initializer list and lifetime of its content
Edit 2: I was able to reproduce the error with your given code, it was not the content of the initializer_list ctor.
The problem seems to be the lifetime of the objects inside the initializer list.
When I move the declaration of the pairs for the unordered map out of the initializer_list for the unordered map, everything works fine:
std::pair<ItemRarities, ChanceSelector<int>> pair1( ItemRarities::Cracked,
{ ChanceSelector<int>(
{ ChancePair( int, 100, 0 ) } ) } );
std::pair<ItemRarities, ChanceSelector<int>> pair2( ItemRarities::Normal,
{ ChanceSelector<int>(
{ ChancePair( int, 80, 0 ),
ChancePair( int, 20, 1 ) } ) } );
std::unordered_map<ItemRarities, ChanceSelector<int>> chances = {
pair1,
pair2
};
I'm not completely sure why this is a problem, but I think is comes from the {} in the initializer list, those objects might get deleted, when leaving the first {} and before entering the actual intializer_list for the unordered_map

Why does array size initialization differ inside or outside a class/struct/...?

The first example will automagically know it's size by the elements it is initialized with.
The second example requires you to explicitly define the length of the array (to get it working).
Why is this the case? Is there a reason behind this?
Array initialization outside a class/struct/...
const int intArray[] = { 0, 1, 2 };
struct Example
{
int SizeOfArray()
{
return sizeof(intArray);
}
};
Array initialization inside a class/struct/...
Faulting
struct Example
{
const int intArray[] = { 0, 1, 2 };
int SizeOfArray()
{
return sizeof(intArray);
}
};
Error: cannot specify explicit initializer for arrays
Solution
struct Example
{
const int intArray[3] = { 0, 1, 2 };
int SizeOfArray()
{
return sizeof(intArray);
}
};
I think it's because in-class/struct initialization is just syntactic sugar for writing part of initializer list in a different place. According to this, your faulting example is equal to the following:
struct Example
{
const int intArray[];
Example() :
// start of compiler-generated pseudo code
intArray[0](0),
intArray[1](1),
intArray[2](2)
// end of compiler-generated pseudo code
{
}
int SizeOfArray()
{
return sizeof(intArray);
}
};
The code above obviously defines an incomplete structure of unknown size, just as #harper said in his comment.
P.S. Couldn't find proof in the standard, but I'm pretty sure that it covers this case, probably implicitly.