template question

Started by
20 comments, last by Impz0r 17 years, 8 months ago
Heya, i've got a question about templates. Lately i played around with templates and tried something like:

namespace math {
 template<typename T> inline T sqrt (T value) {
  return (::sqrt (value));
 }
}

int main (...) {
 float foo = math::sqrt (4.0f); // does work
 long bar = math::sqrt (4); // does not work, the compiler reports: Error 2 error C2668: 'sqrt' : ambiguous call to overloaded function
}

Which makes sense, because the orginal sqrt() function does only take, "long double", "double", "float". So far so good. Now the real question. *g* Is there are a way to detect, if the type T needs to be typcasted and if so, build the function with the required typecast ? Or does this only works with explicit specialization ? Please bear with me and the example, i'm not so into templates ;) Thanks in advance! Impz0r
Stay Evil & Ugly!
Advertisement
I believe that the most effective way to do this would be via template specializations. Which is also the exact mechanism for accomplishing what you want to do.
daerid@gmail.com
For something simple like you example I agree with daerid. for something more complex you could use std::type_info and typeid(T)
the only way to do what you want is manually, because their could never be a case of having the compiler choose automatically which conversion to use in the situation in which the problem is that it doesn't know which version to use.

Looking at what you are doing, there are 2 distinct pieces:

1. You are wrapping the stand-alone overloaded sqrt() functions inside namespace math. You choose to do this using a templated function which should work perfectly well.

2. You want to provide additional overloads which allow the user to pass types other than the 2 supported by the source functions.

The problem with 2 is ... WHICH overloads, if you do provide such a function you are actually hiding the conversion from the client which uses it, and thereby likely making them unaware that there was even a choice.

It would be difficult to determine which implementation is better for:

int val;
...
int result = math::sqrt(val);

should you use float for efficiency, double for accuracy, or long double for reasons largely tied to a particular platform.

What you should do depends on your purpose ... if writing a general math wrapper library you should likely not add overloads ... if you are writing a library for your specific need, then you should simply pick one version (float, double, etc) and use it for integral type up-conversions.
Thanks daerid!


So that means, i define for every type another template function like:

namespace math { template<typename T> inline T sqrt (T value) {  return (::sqrt (value)); } template<> inline long sqrt (long value) {  return (static_cast<long>(::sqrt (static_cast<float> (value)))); } ... etc ...}


Hmm doesn't that kinda defeat the purpose of a template ?


@Anonymous Poster

How exactely would look something like this ?


@Xai

First off, thanks for your replay!
So in your second point you mean, let the user typecast like:
namespace math { template<typename T> inline T sqrt (T value) {  return (::sqrt (value)); } float foo = math::sqrt (4.0f); long bar = static_cast<long> (math::sqrt (static_cast<float> (4))); // something like this ?}


Well that would work i guess and is most likely the "cleanest" solution?


Impz0r
Stay Evil & Ugly!
You can do it with boost::enable_if and Boost.TypeTraits:

#include <cmath>#include <boost/type_traits.hpp>#include <boost/utility/enable_if.hpp>namespace math {  template< class T >  typename boost::disable_if< boost::is_floating_point< T >, T >::type  sqrt( T value ) {    return static_cast< T >( std::sqrt( static_cast< double >( value ) ) );  }  template< class T >  typename boost::enable_if< boost::is_floating_point< T >, T >::type  sqrt( T value ) {    return std::sqrt( value );  }}int main() {  float foo = math::sqrt( 4.0f );  long bar = math::sqrt( 4 );}


There might be a more elegant way by importing std::sqrt into the math namespace. If that doesn't work, you can also use enable_if as a final default parameter (I like this version better, though).


jfl.
Quote:Original post by Impz0r
Thanks daerid!


So that means, i define for every type another template function like:

*** Source Snippet Removed ***

Hmm doesn't that kinda defeat the purpose of a template ?


Yep. One way to modify only part of the code for each type, but still have a generic function is to use traits.

template<typename T> class CastType;template<> class CastType<int> {public:typedef float Type;};... etc ...template<typename T> inline T sqrt (T value) {  return (static_cast<CastType<T>::Type>(::sqrt (static_cast<float> (value))));}


(Might need a typename in there when referencing "Type", I always forget those.)

Basicly this lets you replace part of your generic function with specific code for each type (in this case the cast), while keeping the rest of the code generic.
Thanks jflanglois!

Unfortunately i'm not quite sure if i follow your solution. I haven't used those boost templates till now. Does boost check the typeid behind the scene to check what kind of a type it was given ? Those checks aren't executed at runtime are they ?

And what did you mean with:
Quote:
"There might be a more elegant way by importing std::sqrt into the math namespace"
?


Impz0r
Stay Evil & Ugly!
Quote:Original post by Impz0r
Thanks jflanglois!

Unfortunately i'm not quite sure if i follow your solution. I haven't used those boost templates till now. Does boost check the typeid behind the scene to check what kind of a type it was given ? Those checks aren't executed at runtime are they ?

No, it's all at compile time. The typetraits look like what Solias does with his CastType class: for instance, is_integral< int > is a spcialization of is_integral< T > where is_integral::value is true.

Quote:And what did you mean with:
Quote:
"There might be a more elegant way by importing std::sqrt into the math namespace"
?

I meant putting a using std::sqrt; as a default instead of implementing math::sqrt in terms of std::sqrt.

[edit: Um, stupid me. I meant this:
namespace math {  template< class T >  typename boost::disable_if< boost::is_floating_point< T >, T >::type  sqrt( T value ) {    return static_cast< T >( std::sqrt( static_cast< double >( value ) ) );  }  using std::sqrt;}int main() {  float foo = math::sqrt( 4.0f );  long bar = math::sqrt( 4 );}
]
Thanks Solias & jflanglois!


So to get it right, the solutions of you both are quite similar? Except i don't have to do much, if i'm using boost ? :p

One question about your solution jflanglois. If i read it correctly, all types which are not floats going through the first function and the rest through the second one ? Does this mean, i don't have to implement a bunch of explicit template functions for all kind of types then ? Only the two and i'm done ?

[Edit]
About the "using std::sqrt", i'm not quite sure what it does there. Could you please explain?



Impz0r
Stay Evil & Ugly!

This topic is closed to new replies.

Advertisement