Save a value of enum type in ValueTree

I am able to save/load primitive values to/from a ValueTree but I wonder if it’s possible to store/load an enum value without having to casting it to/from an integer?

Why would you need to cast it? An enum value is already int compatible. Personally, I have never actually defined a member or parameter as an enum type, always as an integer. I only use the actual enum to initialize the int (or to check a range of the int).

If it’s an enum class you’d need to explicitly cast it (IIRC).

The only way to put an enum into a ValueTree is to cast it, and the simplest cast would be to an int (you could write a converter to any other type that juce::var supports, like a juce::String but that’d be significantly more expensive). You could write a template overload of juce::VariantConverter to wrap the casting if you like, then you only need to use the toVar and fromVar methods which I find can be a look cleaner.

2 Likes

What I do is cast first to the underlying type, then to integer. Most enums will use integer as their underlying type, but I think it’s safest to be very explicit, and if integer was the underlying type, then the second cast here will be a no-op:

to save:

template < typename Type >
juce::var fromEnum (Type value)
{
    return static_cast< int > (static_cast< std::underlying_type_t< Type > > (value));
}

to load:

template < typename Type >
Type toEnum (const juce::var& var)
{
    return static_cast< Type > (static_cast< std::underlying_type_t< Type > > ((int) var));
}
4 Likes

TIL of std:: underlying_type_t!

3 Likes

enums (of which I only use enum class now) provide a form of safety in that you can’t (without casting) use values that are not valid. I use them more and more instead of int, or bool, for function parameters, which makes it impossible to get the parameters in the wrong order.

1 Like

@cpr2323 I have also started using enum class more and more, instead of plain enums. But I found this thread while searching for how people store enum class values in ValueTrees. How do you handle that?

Casting to an int. All of my ValueTree operations happen through helper classes, so I only write those casts twice. This means I only write bounds asserts once too.

2 Likes

i always have a toString method along with my enum classes like so:

enum class InterpolationType { Lerp, Spline, Sinc, NumTypes };
inline juce::String toString(InterpolationType t) {
    switch(t)
    {
    case InterpolationType::Lerp: return "Lerp";
    // and so on
    default: return "";
    }
}

that way you can always get a clear identifier for your types by saying

valueTree.setProperty("InterpolationType", toString(interpolationType), nullptr);

i think that’s an ok solution but it becomes a little tedious the bigger your project gets so if anyone knows if there’s a way to directly cast the name of any enum to some sort of string type that would be very helpful

1 Like

This is not a bad suggestion, because saving the value as a string allows you to add and reorder the enum values in the future, plus there’s the added benefit of the serialized data itself being human-readable. So, +1 for this :blush:

As far as converting the enum value to a string, there are a couple answers in this StackOverflow thread which do seem to satisfy DRY, but most of them involve macros. So, pick your poison, I guess.

1 Like

i see what you mean :smiley: technically it’s DRY but… WHAT IS THIS?? anyway, thank you! i’ll consider it. um… but i do gotta say… i have not checked out recent updates of the c++ standard yet. i only know about the spaceship operator and everything else i read was kinda confusing. so what i’m asking myself atm is how come they don’t spend some time actually improving this enum situation a bit. i mean, idk about everyone else here, but to me enums are really important parts of my code. they make things so much more descriptive and describing state is just a thing that is often crucial to me. and i suppose i’m not the first one to ask questions like that, am i? i really hope c++ will just integrate a solution into the language at some point rather than forcing us to use this cryptic looking stuff

1 Like

I agree, native support for enum->string would be a great language feature to have.

The C++ standards committee seems to suffer the same issues that many committees do, that some of the best improvements get bogged down in debates and perhaps even shelved for quite some time. I think Bjarne Stroustroup once said that he’s been trying to get concepts into the language since like the 80s or something…

The top of my personal wishlist is reflection, which I’ve heard is supposedly supposed to be coming? Perhaps? But the news is conflicting, and I’m not holding my breath for every compiler to implement full support any time soon.

Interesting approach. Do the helper classes own the ValueTree and keep it private?

And if so, do you allow listeners to the ValueTree? Or maybe provide a Value for each property, to enable Value listeners (rather than ValueTree listeners)?

It doesn’t even necessarily have to be a class that encapsulates the ValueTree, you could just write a couple of free functions:

template<typename Type>
void saveToTree (ValueTree& tree, const Type& object, const String& propertyName)
{
    if constexpr (std::is_enum_v<Type>)
         tree.setProperty (propertyName, 
                           static_cast<int>(object),
                           nullptr);
    else 
        tree.setProperty (propertyName, 
                          VariantConverter<Type>::toVar (object),
                          nullptr);
}

template<typename Type>
void loadFromTree (Type& object, const ValueTree& tree, const String& propertyName)
{
    if (! tree.hasProperty (propertyName))
        return;

    if constexpr (std::is_enum_v<Type>)
        object = static_cast<Type> ((int) tree.getProperty (propertyName));
    else
        object = VariantConverter<Type>::fromVar (tree.getProperty (propertyName));
}

That sounds similar to juce::CachedValue - which uses juce::VariantConverter under the hood to convert to and from juce::var.

The ValueTree’s are not considered private. They are the method that data gets passed around, but anyone that wants to interact with a particular VT use a ‘wrapper’, which encapsulates the particular data with typesafe setters/getters and callbacks. If the data has a collection, the wrapper also provides features like forEachX (std::function<void (ValueTree xVT)>). The wrapper can be given either the specific VT, or a parent of the VT (in which case it locates its data from the child). Most of the code passes root ValueTree’s, giving code access to all of the data in the system, and allowing the wrapping of any of the VT’s.

As mentioned further down, this is similar to CachedValue, but it is for a group, or domain, of data.

1 Like

@cpr2323 OK, I’m understanding the technical aspects of what you’re describing, and encapsulating particular sets of data with typesafe setters/getters (which can also do value checking, to keep values in-bounds) sounds like a robust way to protect the data inside the ValueTrees.

However, I’m still not clear how that approach meshes with allowing ValueTree listeners (or maybe you don’t use listeners in your design?). Say you’ve got a class that’s listening to a ValueTree, and the valueTreePropertyChanged gets hit. It passes in 2 parameters: a reference to the ValueTree, and a property name – but if you want to get the value of that property, you have to grab it for yourself. So under your wrapper system, in a listener callback, would you have to look up which wrapper to use, to interact with the data in a particular ValueTree?

Also, while I like the convenience of a built-in iterator like the forEachX method you mention, it feels like that breaks the encapsulation of the data. Whatever std::function you pass in would have to know about the internal organization of the ValueTree in order to do anything useful.

Ah. I implement callback handlers in the wrapper class, and they will invoke a onThingChanged std::function callback, in the same way the onClick handler works for buttons. And, exactly like a ValueTree, you can’t copy this, because of the callbacks. But, also like the VT itself, the wrapper is super light weight. This should help clarify:

void SomeClass::SomeClass (ValueTree root)
{
	// finds the correct valuetree child
    domainSpecificData.wrap (root);
	
	// sets up a callback for if that data changes
	domainSpecificData.onThingValueChange = [this] (DataType data) { button.setToggleState (data == 33); };
	
	// init gui with current value with a getter
	setGuiThingForItem (domainSpecificData.getThingValue ());
	
	// setup button handled to use setter
	button.onClick = [this] () { domainSpecificData.setThingValue (button.getToggleState (): 33 ? 55)};
}
1 Like

This doesn’t break the encapsulation, as the whole system works by passing ValueTrees and wrapping them, instead of passing wrappers. which means you would also have to update API’s if you wanted to pass multiple objects. The passed around VT’s can be considered Opaque, until you want to use it, then you wrap it and it has meaning. It will also assert if you pass in the wrong VT, which has saved us so much time. We have a SongLibrary, which has a forEachSong iterator, you simple wrap the incoming VT in a Song wrapper and you are good to go. I also implement static members that allow access without wrapping, so there is a Song::getName (); and a Song::getName (ValueTree vt);. These static members tend to be added when needed. So, for example, if I were searching that name field from the song, I wouldn’t have to execute the whole wrap operation, I could use the static function (which also gets called internally for the getName (). Let me know if this helps.

1 Like