Cannot instantiate a value tree using an initializer list created in a different scope

Hi All,

I’m getting an assert in this constructor (everything is invalid, type, value, valueToCopy)

var::var (const var& valueToCopy)  : type (valueToCopy.type)
{
    type->createCopy (value, valueToCopy.value);
}

when attempting to create a value tree using a lambda to create an initializer list for the properties, like this:

//1- invalid value tree example with lambda initializer list
auto getInitializerListFromLambda = []()-> std::initializer_list<juce::NamedValueSet::NamedValue>
{
    return
    {
        { "id", {.3} },
        {"value", {0.5}}
    };
};

ValueTree invalidTree =  {"NotWorking", getInitializerListFromLambda()};

By comparison, using a local initializer list works:

//2- valid value tree example with local initializer list
std::initializer_list<juce::NamedValueSet::NamedValue> localList =
{
    { "id", var(.3) },
    {"value", var(0.5)}
};

auto validTree = ValueTree(VoodooIds::effectStateId, localList);

Googling this tells me that the problem may be that the content of the initializer list may not persist after the lambda scope… but then why is this working fine?

//3- valid vector example with initializer list created in lambda
auto foo = []() -> std::initializer_list<int> { return {1, 2}; };
std::vector<int> bar{foo()};
for (int x : bar) { std::cout << x << "  "; };

I have tried a number of things, such as using explicit NamedValueSet constructors, returning a pointer to a dynamically created initializer list in the lambda, different types of constructors for the vars… nothing works. Could it be related to the std::move in here?

ValueTree::ValueTree (const Identifier& type,
                      std::initializer_list<NamedValueSet::NamedValue> properties,
                      std::initializer_list<ValueTree> subTrees)
    : ValueTree (type)
{
    object->properties = NamedValueSet (std::move (properties));

    for (auto& tree : subTrees)
        addChild (tree, -1, nullptr);
}

From https://en.cppreference.com/w/cpp/utility/initializer_list:

Copying a std::initializer_list does not copy the underlying objects.

Your third example is working “fine” because you are on Linux or macOS, and the C++ runtime doesn’t bother “destroying” the integers and rewriting the memory they were occupying. However, the Debug C++ runtime on Windows does it and I get 1767248 13166200, or 6158384 10937976, or 16514620 10937976, etc. but I don’t get 1 2.

Here is an example that shows when the objects in the std::initializer_list are destroyed:

#include <initializer_list>
#include <iostream>

struct talking_int
{
  talking_int(int v) : value(v) {
    std::cout << "constructed " << value << "\n";
  }
  ~talking_int() {
    std::cout << "destroyed " << value << "\n";
  }

  int value;
};

std::initializer_list<talking_int> make_ints() {
  return {1, 2};
}

void use_ints(std::initializer_list<talking_int> ints) {
  std::cout << "there are " << ints.size() << " ints\n";
}

int main() {
  use_ints(make_ints());
}

You can try it in your browser there: http://cpp.sh/2743b

When executing it, it prints:

constructed 1
constructed 2
destroyed 2
destroyed 1
there are 2 ints

which shows that the objects in the std::initializer_list are destroyed before they can be used.

3 Likes

Wow, thank you for the thorough reply! I was on windows, but it was probably a fluke ;). Cheers!

1 Like