Forcing all function template parameters to deduce the same type

D

Dan Krantz

I have the following template to ensure that a given number (val) falls into
a range (between vmin & vmax):

template<typename T> T ForceNumericRange( const T& val, const T& vmin, const
T& vmax)
{
T retVal = val;

if ( retVal < vmin )
retVal = vmin;
else if ( retVal > vmax )
retVal = vmax;

return retVal;
}

Here's the call:

float comm;
....
comm = ForceNumericRange( comm, 0.0, 100.0);

The compiler (Visual Age C++ 5 under AIX 4.3) complains: The function
template parameter "T" has been deduced to have two values: "float" and
"double"

When I took the .0 off the constant parameters, it complained about T being
float & int. I solved the problem with this change:

comm = ForceNumericRange( comm, 0.0F, 100.0F);

but it's hokey, since I don't want the caller to have to explicitly specify
the type for the range constants.

Is there a way to declare the template such that the types of the 2nd and
3rd parameter are always deduced from the type of the first argument? I'm
pretty sure I could give parameters 2 & 3 a different type declaration, and
rely on runtime conversions between the different types, but I'd like this
resolved at compile time; also, that approach might fail if vmin was a
variable and vmax was a constant.

Any suggestions?
 
S

Steve Pope

Dan Krantz said:
I have the following template to ensure that a given number (val) falls into
a range (between vmin & vmax):
template<typename T> T ForceNumericRange( const T& val, const T& vmin, const
T& vmax)
{
T retVal = val;

if ( retVal < vmin )
retVal = vmin;
else if ( retVal > vmax )
retVal = vmax;

return retVal;
}

Here's the call:

float comm;
...
comm = ForceNumericRange( comm, 0.0, 100.0);
The compiler (Visual Age C++ 5 under AIX 4.3) complains: The function
template parameter "T" has been deduced to have two values: "float" and
"double"
When I took the .0 off the constant parameters, it complained about T being
float & int. I solved the problem with this change:

comm = ForceNumericRange( comm, 0.0F, 100.0F);

but it's hokey, since I don't want the caller to have to explicitly specify
the type for the range constants.
Is there a way to declare the template such that the types of the 2nd and
3rd parameter are always deduced from the type of the first argument?

Not that I can think of.

My two comments, neither of which answers your question, is that it is
uncommon to want to use float instead of double, except when
doing I/O or packing data or similar; and that using const

Steve
to define constants, instead of placing literals in function
arguments, is useful.
 
H

Howard Hinnant

"Dan Krantz said:
I have the following template to ensure that a given number (val) falls into
a range (between vmin & vmax):

template<typename T> T ForceNumericRange( const T& val, const T& vmin, const
T& vmax)
{
T retVal = val;

if ( retVal < vmin )
retVal = vmin;
else if ( retVal > vmax )
retVal = vmax;

return retVal;
}

Here's the call:

float comm;
...
comm = ForceNumericRange( comm, 0.0, 100.0);

The compiler (Visual Age C++ 5 under AIX 4.3) complains: The function
template parameter "T" has been deduced to have two values: "float" and
"double"

When I took the .0 off the constant parameters, it complained about T being
float & int. I solved the problem with this change:

comm = ForceNumericRange( comm, 0.0F, 100.0F);

but it's hokey, since I don't want the caller to have to explicitly specify
the type for the range constants.

Is there a way to declare the template such that the types of the 2nd and
3rd parameter are always deduced from the type of the first argument? I'm
pretty sure I could give parameters 2 & 3 a different type declaration, and
rely on runtime conversions between the different types, but I'd like this
resolved at compile time; also, that approach might fail if vmin was a
variable and vmax was a constant.

Any suggestions?

You can use "concepts" to implement "mixed-mode" arithmetic functions.
The first thing you need is a C++03 emulation of concepts:

template <bool, class T = void> struct where {};
template <class T> struct where<true, T> {typedef T type;};

This is more famously known as "enable_if" and can be found at
www.boost.org. I've also called it "restrict_to" in the past. But it's
the same beast whatever you call it.

With this struct, and maybe a little help from std::tr1::type_traits (or
boost::type_traits) you can constrain your template parameters. In this
case you could make all three arguments different template parameters
(say T, U and V), but constrain U and V such that they are both
convertible to T. That would look like:

#include <tr1/type_traits>

template <bool, class T = void> struct where {};
template <class T> struct where<true, T> {typedef T type;};

template<typename T, class U, class V>
typename where
<
std::tr1::is_convertible<U, T>::value &&
std::tr1::is_convertible said:
ForceNumericRange( const T& val, const U& vmin, const V& vmax)
{
T retVal = val;

if ( retVal < vmin )
retVal = vmin;
else if ( retVal > vmax )
retVal = vmax;

return retVal;
}

Other constraints might work for you as well. For example you might
want to constrain each of T, U and V to be arithmetic types:

template<typename T, class U, class V>
typename where
<
std::tr1::is_arithmetic<T>::value &&
std::tr1::is_arithmetic<U>::value &&
std::tr1::is_arithmetic said:
ForceNumericRange( const T& val, const U& vmin, const V& vmax);

There is a big push on the standards committee to make concepts part of
the language for C++0X. This would clean up the syntax and make it
easier to build and reuse complex constraints. If accepted, it will
probably use "where" as a keyword. So if you use "where" now, note that
it may not compile in the future. You can view this as either a feature
or a bug. When it doesn't compile, you'll know it is time to upgrade to
the language supported variant.

Finally, you could go with 3 unconstrained templates:

template<typename T, class U, class V>
T
ForceNumericRange( const T& val, const U& vmin, const V& vmax);

This is what I refer to as "overly generic" code and is prone to
breakage in the case that the function name is part of an overload set
(if ForceNumericRange is overloaded, even in other namespaces). With a
name like ForceNumericRange, unintended overloading seems unlikely. But
it is easy to fall into the trap of using overly generic coding
techniques with very common names (as was done in the standard):

distance
advance
copy
fill
rotate

-Howard
 
H

Howard Hinnant

Howard Hinnant said:
You can use "concepts" to implement "mixed-mode" arithmetic functions.
The first thing you need is a C++03 emulation of concepts:

template <bool, class T = void> struct where {};
template <class T> struct where<true, T> {typedef T type;};

Oh, I got so carried away with concepts that I neglected to give you one
of the simplest techniques. :)

You can do exactly this:
Is there a way to declare the template such that the types of the 2nd and
3rd parameter are always deduced from the type of the first argument?

with the following code:

template <class T>
struct identity
{
typedef T type;
};

template<typename T>
T
ForceNumericRange(const T& val, const typename identity<T>::type& vmin,
const typename identity<T>::type& vmax);

I.e. this puts the second and third arguments into a non-deducible
context. And you still have the option of further constraining T using
"where" if necessary.

-Howard
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,755
Messages
2,569,537
Members
45,023
Latest member
websitedesig25

Latest Threads

Top