I’m not saying that it’s not a valid addition but I think placement has an important role to play here.
Firstly though, your 6 step example isn’t usually how you would create and add a child to a parent. There’s no reason you can’t just do this (assuming all your Identifiers
are static which they should be for performance reasons).
ValueTree parent (IDs::TRACK);
parent.addChild (ValueTree (IDs::CLIP), -1, nullptr);
So it’s really only two steps. (Removing explicit
from the ValueTree
constructor would also remove the need to specify that in the second line but maybe there’s a reason for this I’m missing…).
However there are good reasons to prefer non-member non-friend functions to member functions as I previously stated. For a start these are two good articles:
http://www.gotw.ca/gotw/084.htm
What I worry most about however is that providing functionality only as member functions has some serious implications on how we reason about a class.
Firstly, keeping only the minimum amount of member functions keeps the API small so it’s easy to see what a class “can do”. This is the base level for abstractions we’re building. It provides a small, single place to quickly ascertain the core functionality of a class.
ValueTree is actually a really good example where this is important because it’s useful to understand its transactional nature (you can set a property, add/remove/reorder children and re-assign a tree). Each of these things has a corresponding listener callback. The more methods the class has, the more unclear this transactional nature becomes.
To me, it’s a lot more conceptually clean to think of what a class “can do” and then provide a number of non-member functions to aid common tasks. This is effectively a second layer of abstraction and tells us not what a ValueTree “can do” but what “we can do with” a ValueTree.
My real reason for disliking adding member functions is a bit simpler than all that though. If things in JUCE are only ever provided as member functions it’s easy for users to only ever use the APIs provided and miss that they can add their own layers of abstractions with non-member functions. This seems to be an increasing issue over the past year or so with a slew of requests that often end in “why don’t you write a free function?”. If common use cases are provided as free functions in JUCE, it’s not so much of a jump for users to realise they can write code in a similar way.
Also, things are far more likely to be added as non-member functions rather than bloat the core class API. There’s at least 3 versions of appendChild
I can think of which could be useful:
/** Returns the parent so you can chain multiple children additions easily. */
ValueTree appendChild (ValueTree& parent, const ValueTree& child, UndoManager*);
/** Returns the child so you can act on that*/
ValueTree appendChild (ValueTree& parent, const ValueTree& child, UndoManager*);
/** Returns the new child. */
ValueTree appendChild (ValueTree& parent, const Identifier& childID, UndoManager*);
And while we’re at it, it’s probably useful to have versions either without UndoManager
parameters or have them default to nullptr
in the interest of terseness.
Plus we could have versions that insert at the front, similar to push_front
.
I appreciate that I’ve derailed this thread a bit and I don’t really have a problem with adding the method you’ve suggested but I think it does start a larger conversation about the best ways of adding things and the implications they have on how users view a class.
I think it is a benefit of JUCE that it does force you to think about the functionality of a class by forcing the index
and UndoManager
when adding children. Similarly I think that having all the listener callbacks pure virtual is useful (again it’s very easy to write named abstractions if you need only a single callback. I know I’ve personally avoided bugs by being forced to consider these things.
One final thing is that if we do ever get unified call syntax the use of these two methods would be identical (although judging by recent standard meetings it looks unlikely we’ll ever get that due to overloading additions that can break code…).
Once we make the initial break of insisting everything has to be a member function it really does open up a lot more doors and abstraction possibilities…