9
\$\begingroup\$

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

template<typename T>
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<float> and interpolate<int> 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<typename T>
struct RefTrait { typedef const T Ref; }

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

template<typename T>
T interpolate(Buffer& buffer, typename RefTrait<T>::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<float>(buffer, floatVal, 0.5f);
vectorVal = interpolate<Vector>(buffer, vectorVal, 0.5f);

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

\$\endgroup\$
8
  • 2
    \$\begingroup\$ Is there a particular reason you don't want to pass ints, floats, etc. by reference? \$\endgroup\$
    – Michael K
    Feb 9 '11 at 16:10
  • \$\begingroup\$ This is from a realtime app, and if this function is called a lot of times a frame then the extra dereferencing would be undesirable. \$\endgroup\$
    – tenpn
    Feb 9 '11 at 16:20
  • 3
    \$\begingroup\$ @tenpn: If the function is inlined, then there will be no extra dereferencing to worry about. If it isn't, then that's what you need to fix; the cost of the function call will be much higher than the cost of the dereference. \$\endgroup\$ Feb 9 '11 at 18:20
  • \$\begingroup\$ But you can't guarentee inlining, it's up to the compiler even if you use the keyword. But anyway I'm interested in the academic point - can I get rid of the reference without impacting client code? \$\endgroup\$
    – tenpn
    Feb 10 '11 at 8:09
  • \$\begingroup\$ Is there any reason you can't just make a second template like "interpolateByRef" for the ones you want to use references on? Seems a mite bit easier than having to explicitly define separate traits per type. \$\endgroup\$ Feb 11 '11 at 15:28
5
\$\begingroup\$

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<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);
}

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.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.