Note that the problem can be reproduced by running the snippet below (I use wandbox with gcc 9.1)
So I have a std::array
(size 2 for simplicity) of std::variant
of two custom types (Normal
and Special
) Normal
is specified as first type so upon class construction, the array is default-constructed with Normal
objects. I change some internal data members of the first element of the array and print it out. Looks fine.
Now I want to set the second element of the array to Special
object. I tried doing this both by assigning to a new value and using emplace
according to this tutorial (https://www.bfilipek.com/2018/06/variant.html#changing-the-values)
However, when I try to change the internal data members of the second object (now typed Special
) it seems like I'm not operating on the object in the original arrays. Print out results show default value of construction (0 in this case) I am new to using std::variant
so I don't have a clue why it would be the case. How can I get the actual reference to the recently type-change variant object in my array?
#include <iostream>
#include <memory>
#include <cstring>
#include <array>
#include <variant>
struct Normal {
struct Header {
std::array<uint8_t, 2> reserved;
};
Normal() : frame{0}, payload{reinterpret_cast<uint8_t*>(frame + sizeof(Header))} {}
constexpr static auto LENGTH = 10;
uint8_t frame[LENGTH];
uint8_t* payload;
};
struct Special {
struct Header {
std::array<uint8_t, 3> reserved;
};
Special() : frame{0}, payload{reinterpret_cast<uint8_t*>(frame + sizeof(Header))} {}
constexpr static auto LENGTH = 11;
uint8_t frame[LENGTH];
uint8_t* payload;
};
std::array<std::variant<Normal, Special>, 2> handlers;
Normal* normal_handler;
Special* special_handler;
int main() {
auto& nh = std::get<Normal>(handlers[0]);
memset(nh.payload, 3, 3);
normal_handler = &nh;
handlers[1].emplace<1>(Special{});
auto& sh = std::get<Special>(handlers[1]);
memset(sh.payload, 4 ,4);
// memset(std::get<Special>(handlers[1]).payload, 4, 4);
special_handler = &sh;
for (int i = 0; i < 10; i++) {
// Expect 3 bytes from 3rd bytes = 3
std::cout << (int) normal_handler->frame[i] << " ";
}
std::cout << std::endl;
for (int i = 0; i < 11; i++) {
// Expect 4 bytes from 4th bytes = 4
std::cout << (int) special_handler->frame[i] << " ";
// std::cout << (int) std::get<Special>(handlers[1]).frame[i] << " ";
}
}
Your issue isn't related to std::variant
, the following code shows the same behaviour:
#include <iostream>
#include <memory>
#include <cstring>
struct Special {
struct Header {
std::array<uint8_t, 3> reserved;
};
Special() : frame{0}, payload{reinterpret_cast<uint8_t*>(frame + sizeof(Header))} {}
constexpr static auto LENGTH = 11;
uint8_t frame[LENGTH];
uint8_t* payload;
};
int main() {
Special s1;
s1 = Special{};
memset(s1.payload, 4 ,4);
for (int i = 0; i < 11; i++) {
// Expect 4 bytes from 4th bytes = 4
std::cout << (int) s1.frame[i] << " ";
}
}
This line:
s1 = Special{};
Creates a temporary Special
object and then assigns it to s1
. The default copy and move constructors will set s1.payload
to the value of payload
in the temporary. Therefore s1.payload
is a dangling pointer to frame
in the temporary object and the rest of your code therefore has undefined behaviour.
The simplest fix is to change the payload
member into a function:
#include <iostream>
#include <memory>
#include <cstring>
struct Special {
struct Header {
std::array<uint8_t, 3> reserved;
};
Special() : frame{0} {}
constexpr static auto LENGTH = 11;
uint8_t frame[LENGTH];
uint8_t* payload() { return &frame[sizeof(Header)]; }
};
int main() {
Special s1;
s1 = Special{};
memset(s1.payload(), 4 ,4);
for (int i = 0; i < 11; i++) {
// Expect 4 bytes from 4th bytes = 4
std::cout << (int) s1.frame[i] << " ";
}
}