HiveBrain v1.2.0
Get Started
← Back to all entries
patterncppMinor

Interpolating given value and legacy value from serialization buffer

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
bufferserializationvalueinterpolatingandfromgivenlegacy

Problem

Here's a function that interpolates between a given value and a value fetched out of a legacy serialization buffer:

template
T interpolate(Buffer& buffer, const T currentValue, const float prop)
{
    T bufferValue;
    buffer.readT(&buferValue);
    return currentValue + (bufferValue-currentValue)*prop;
}


This works great for interpolate and interpolate and so on. However if I want to pass a more complex structure such as a vector, I'd rather the currentValue parameter was passed by reference instead of by value. I can use overloading to handle that situation:

// in addition to original function
Vector interpolate(Buffer& buffer, const Vector& currentValue, float prop)
{
    Vector bufferValue;
    buffer.readT(bufferValue);
    return currentValue + (bufferValue-currentValue)*prop;
}


Even if you rip out the lerp into a helper function, it still isn't ideal to repeat the function like this when the only difference from the original is the & parameter, especially if there's more than one type I'd like to pass by reference.

I can use traits to auto-detect when to use a reference:

// to replace original function

template
struct RefTrait { typedef const T Ref; }

template<>
struct RefTrait { typedef const Vector& Ref; }

template
T interpolate(Buffer& buffer, typename RefTrait::Ref currentValue, const float prop)
{
    T bufferValue;
    buffer.readT(&buferValue);
    return currentValue + (bufferValue-currentValue)*prop;
}


However now the compiler can't induce the type of T by default, and the calling code has to explicitly state type:

floatVal = interpolate(buffer, floatVal, 0.5f);
vectorVal = interpolate(buffer, vectorVal, 0.5f);


Is there anyway to have compact calling code as well as a single defined function?

Solution

If I understand your question correctly, using the RefTrait policy in your second solution is acceptable but you want to avoid specifying template parameters from the client code that's using it.

If so then perhaps one possible idea is to create an inline wrapper function around your interpolate for marshaling the call so type deduction happens automatically:

template <>
struct RefTrait { typedef const Vector &Ref; };

template 
T interpolateImp(Buffer& buffer, typename RefTrait::Ref currentValue, const float prop)
{
    T bufferValue;
    buffer.readT(&bufferValue);
    return currentValue + (bufferValue-currentValue)*prop;
}

template 
inline T interpolate(Buffer& buffer, T ¤tValue, const float prop)
{
    return interpolateImp(buffer, currentValue, prop);
}


It doesn't avoid the overloading but it'll at least help with the automatic type-deduction when you try to use it:

floatVal  = interpolate(buffer, floatVal, 0.5f);
vectorVal = interpolate(buffer, vectorVal, 0.5f);


And the interpolate wrapper should get optimized away into just a interpolateImp call when the compiler inlines it.

But I would recommend taking a look at what Mike said first and find out really how much of a performance impact this is. If afterwards you still decide to pursue this route there are two things to keep in mind.

  • Caveat with using your current RefTrait. At the moment, T bufferValue; in your interpolate creates a locate T variable but doesn't initialize it. This poses a problem if T = const Vector&. Furthermore, it also means you're unable to change this T object later on should you need to. One possible way to fix it is to also add a valueType to your RefTraits policy. You would then use typename T::valueType to create any locates you would need inside interpolate.



  • Using templates in this matter will reduce code clarity unless you're extra dilgent. The syntax has a matter of exploding in your face and could be difficult to get right especially if you're trying to cover the funny corner cases. Be sure to weight the tradeoffs.



Edit: After thinking about my above code some more, I noticed it could be simplified a bit by just keeping the syntax the same as the OP. I've updated mines to reflect that.

Code Snippets

template <>
struct RefTrait<Vector> { typedef const Vector &Ref; };

template <typename T>
T interpolateImp(Buffer& buffer, typename RefTrait<T>::Ref currentValue, const float prop)
{
    T bufferValue;
    buffer.readT(&bufferValue);
    return currentValue + (bufferValue-currentValue)*prop;
}

template <typename T>
inline T interpolate(Buffer& buffer, T &currentValue, const float prop)
{
    return interpolateImp<T>(buffer, currentValue, prop);
}
floatVal  = interpolate(buffer, floatVal, 0.5f);
vectorVal = interpolate(buffer, vectorVal, 0.5f);

Context

StackExchange Code Review Q#706, answer score: 5

Revisions (0)

No revisions yet.