1 hour ago, matt77hias said:
I find the "Try" a bit ambiguous since the name is also associated with try/catch clauses. "GetAnim" should suffice, for the remainder (i.e. what happens if the index does not match anything?), you should read the specifications of the method, IMHO.
I disagree. try/catch has a sufficiently visually different syntax from a function invocation that I would hope nobody would ever mistake the two.
The implication of the "try" function nomenclature is that the function could fail in a recoverable way, or in a way that allows you to have different behaviour when it fails. Some would argue that exceptions are preferable here; not all of us use exceptions or even think that exceptions in C++ are a good idea. The explicit naming is useful when we want to obviously distinguish between functions that can fail and functions that cannot - eg. in the case where, for performance reasons, you want to access an animation by index without bounds checking. That difference is an important aspect of the functions' semantics.
// returns the animation with the given index, or nullptr if the index is invalid
const Anim* TryAndGetAnim(const int index) const;
// returns the animation with the given index - asserts if the index is invalid,
// this function should never be called when we don't already know that the index is valid
const Anim& GetAnim(const int index) const;
// returns the index associated with the name, or -1 if it fails to find it
int FindAnimIndex(const char* name) const;
// use cases:
if (const Anim* anim = TryAndGetAnim(FindAnimIndex("run")))
{
//...
}
int animIndex = FindAnimIndex("run");
if (animIndex != -1)
{
// use the index here...
const Anim& anim = GetAnim(animIndex);
// ...
}
Having the different name also lets you avoid the fact that you can't overload functions by return type alone, of course.
Quote
Furthermore, it could be beneficial to have multiple ways of accessing the "Anim" with regard of performance (I am not saying they should all have the same name). Of course: make it work -> make it right (refactoring) -> make it fast (profiling/optimization); so alternative ways (if beneficial) should only be added in the end.
I don't see any functional difference between GetAnim(FindAnimIndex("run")) and GetAnim("run"). The performance difference is likely meaningless, depending on how these are used, and I'd argue that the first one is much clearer than the second one even if it's more verbose. I want to be able to tell at a glance whether a search algorithm is happening or whether a simple indexed lookup is happening. Chances are good my peer reviewers (you do have a peer review process on code checkins, right?) are going to want the same thing.
Of course, one could also invert the above paradigm and change the names a bit:
// returns the animation with the given name, or nullptr if we didn't find it
const Anim* TryAndFindAnim(const char* name) const;
// returns the animation with the given name - asserts if we didn't find it,
// this function should never be called when we don't already know that the animation exists
const Anim& FindAnim(const char* name) const;
// returns the index of the animation, or -1 if it isn't in this container or is nullptr
int ComputeAnimIndex(const Anim* anim) const;
As you suggest, you could have both. In that case having different names is an absolute must; note that in my second code example I'm using "Find" rather than "Get." This is because to me "Find" implies a search operation (probably one that's O(n)) while "Get" implies a simple O(1) lookup. Furthermore, as you suggested, I probably wouldn't write the extra functions up front, I'd wait until a need for them was demonstrated. I'd write whichever set of functions was the most common case at the time I needed the functionality.
Ultimately, this still isn't a good use case for overloading. Overloading should be used in cases where there is no semantic difference between the functions and the only difference is the types they take, as with trig functions taking floats or doubles.