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?
-
2\$\begingroup\$ Is there a particular reason you don't want to pass ints, floats, etc. by reference? \$\endgroup\$Michael K– Michael K2011年02月09日 16:10:10 +00:00Commented Feb 9, 2011 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– tenpn2011年02月09日 16:20:08 +00:00Commented Feb 9, 2011 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\$Mike Seymour– Mike Seymour2011年02月09日 18:20:17 +00:00Commented Feb 9, 2011 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– tenpn2011年02月10日 08:09:40 +00:00Commented Feb 10, 2011 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\$TheXenocide– TheXenocide2011年02月11日 15:28:33 +00:00Commented Feb 11, 2011 at 15:28
1 Answer 1
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 ¤tValue, 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 yourinterpolate
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 usetypename T::valueType
to create any locates you would need insideinterpolate
. - 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.