I'm using the yaml-cpp library to parse yaml. Abbreviated sample:
YAML::Node def = YAML::LoadFile(defFile);
for (auto itemPair = def.begin(); itemPair != def.end(); ++itemPair) {
// Grab a reference so `itemPair->second` doesn't need to be copied all over the place
auto& item = itemPair->second;
// A few instances of the below in series
if (item["key"].IsDefined()) { doSomething(item["key"].as<std::string>()); }
// Problem happens here
if (item["issue"].IsDefined()) {
if (!item["issue"].IsMap()) { continue; }
for (auto x = item["issue"].begin(); x != item["issue"].end(); ++x) {
LOG(INFO) << "Type before: " << item.Type() << " : " << itemPair->second.Type();
auto test = x->first.as<std::string>();
LOG(INFO) << "Type after: " << item.Type() << " : " << itemPair->second.Type();
// Using item as a map fails because it no longer is one!
// Next loop attempt also crashes when it attempts to use [] on item.
}
}
The problem happens in the nested loop, where the reference taken at the beginning of the snippet suddenly changes, however the variable it's referencing seems to be unaffected:
I1218 12:44:04.697798 296012 main.cpp:123] Type before: 4 : 4
I1218 12:44:04.697813 296012 main.cpp:125] Type after: 2 : 4
My understanding of references is that they act as an alias for another variable. I understand the yaml library might be doing some magic behind the scenes which would change the underlying data, but I can't comprehend why the reference seems to be getting updated yet the original value remains.
Edit: Some serious mind-blowing behaviour is happening here. The reference gets "reset" back to the correct value after any call to itemPair->second.Type()
. Thus if I add another log call:
LOG(INFO) << "Type after: " << item.Type() << " : " << itemPair->second.Type();
LOG(INFO) << "Type afterer: " << item.Type() << " : " << itemPair->second.Type();
The result:
I1218 12:58:59.965732 297648 main.cpp:123] Type before: 4 : 4
I1218 12:58:59.965752 297648 main.cpp:125] Type after: 2 : 4
I1218 12:58:59.965766 297648 main.cpp:126] Type afterer: 4 : 4
Reproducible example:
test.yaml
:
---
one:
key: x
issue:
first: 1
two:
key: y
issue:
first: 1
second: 2
main.cpp
same as above but with hardcoded test.yaml
, LOG
swapped for std::cout
, and the mock function:
#include <iostream>
#include <yaml-cpp/yaml.h>
void doSomething(std::string x) { std::cout << "Got key: " << x << std::endl; }
int main() {
YAML::Node def = YAML::LoadFile("test.yaml");
for (auto itemPair = def.begin(); itemPair != def.end(); ++itemPair) {
// Grab a reference so `itemPair->second` doesn't need to be copied all over the place
auto& item = itemPair->second;
// A few instances of the below in series
if (item["key"].IsDefined()) { doSomething(item["key"].as<std::string>()); }
// Problem happens here
if (item["issue"].IsDefined()) {
if (!item["issue"].IsMap()) { continue; }
for (auto x = item["issue"].begin(); x != item["issue"].end(); ++x) {
std::cout << "Type before: " << item.Type() << " : " << itemPair->second.Type() << std::endl;
auto test = x->first.as<std::string>();
std::cout << "Type after: " << item.Type() << " : " << itemPair->second.Type() << std::endl;
std::cout << "Type afterer: " << item.Type() << " : " << itemPair->second.Type() << std::endl;
// Using item as a map fails because it no longer is one!
// Next loop attempt also crashes when it attempts to use [] on item.
}
}
}
}
Result:
$ ./build/out
Got key: x
Type before: 4 : 4
Type after: 2 : 4
Type afterer: 4 : 4
Got key: y
Type before: 4 : 4
Type after: 2 : 4
Type afterer: 4 : 4
Type before: 4 : 4
Type after: 2 : 4
Type afterer: 4 : 4