views:

193

answers:

3

I have a templated function that operates on a template-type variable, and if the value is less than 0, sets it to 0. This works fine, but when my templated type is unsigned, I get a warning about how the comparison is always false. This obviously makes sense, but since its templated, I'd like it to be geenric for all data types (signed and unsigned) and not issue the warning. I'm using g++ on Linux, and I'm guessing there's a way to suppress that particular warning via command line option to g++, but I'd still like to get the warning in other, non-templated, cases. I'm wondering if there's some way, in the code, to prevent this, without having to write multiple versions of the function?

template < class T >
T trim(T &val)
{
  if (val < 0)
  {
    val = 0;
  }
  return (val);
}
int main()
{
  char cval = 5;
  unsigned char ucval = 5;

  cout << "Untrimmed: " << (int)cval;
  cval = trim(cval);
  cout << " Trimmed: " << (int)cval << endl;

  cout << "Untrimmed: " << (int)ucval;
  cval = trim(ucval);
  cout << " Trimmed: " << (int)ucval << endl;

 return (0);
}
+5  A: 

For the simple case presented, you'd be definitively better of with the solution presented by Roger Pate.

What you need for the general metaprogramming solution is type_traits. You can either use those from boost, or ones supplied with your STL if modern enough.

namespace detail {

  template < class T >
  T trim_impl(T &val, const std::tr1::true_type& )
  {
    if (val < 0)
    {
      val = 0;
    }
    return (val);
  }

  template < class T >
  T trim_impl(T &val, const std::tr1::false_type& )
  {
    return (val);
  }  

} // end namespace detail

template < class T >
T trim(T &val)
{
  return detail::trim_impl( val, std::tr1::is_signed<T>() );
}

Take note however that is_signed is false_type for floating point numbers (don't ask why). To make the above code work with floating points you'd need to typedef another trait, e.g.

typedef std::tr1::integral_constant< bool, 
            std::tr1::is_signed<T>::value || 
            std::tr1::is_floating_point<T>::value > has_sign;

... and yeah, the deeper you get into metaprogramming the uglier it gets so... disregard this solution and go with the simple one listed by Roger :P.

Kornel Kisielewicz
You missed the `_impl` part and the `detail` namespace for the first two functions.
Georg Fritzsche
Yeah noticed :), thanks
Kornel Kisielewicz
Very good explanation and example for the space used, and don't take my outrage in the edit history too seriously---I just had to fix my name, even if I make that typo sometimes. *\*whistles innocently\** :P
Roger Pate
Oh, my, I'm very sorry for the typo :/
Kornel Kisielewicz
+5  A: 
#include <algorithm>

template<class T>
T& trim(T& val) {
  val = std::max(T(0), val);
  return val;
}

It's not apparent from the question that passing by non-const reference is appropriate. You can change the above return nothing (void), pass by value and return by value, or pass by const& and return by value:

template<class T>
T trim(T const& val);

// example use:
value = trim(value); // likely the most clear solution

Generalize a bit more, even though outside the scope of your question:

template<class T>
T constrain(T const& value, T const& lower, T const& upper) {
  // returns value if value within [lower, upper] (inclusive end points)
  // returns lower if value < lower
  // otherwise returns upper
  assert(lower <= upper); // precondition
  return std::min(std::max(value, lower), upper);
}

template<class T>
T constrain_range(T const& value, T const& lower, T const& upper) {
  // returns value if value within [lower, upper) (exclusive upper)
  // returns lower if value < lower
  // otherwise returns upper - 1
  assert(lower < upper); // precondition
  if      (value <  lower) return lower;
  else if (value >= upper) return upper - 1;
  else                     return value;
}
Roger Pate
+1 : Well, yeah, that's a solution for this particluar case :)
Kornel Kisielewicz
A: 

Great - thank you! I used Roger's method - I probably should have thought about that myself, but didn't. I appreciate everyone's quick responses and suggestions! I wonder if there is a way to leave the less than check in there? The metaprogramming stuff seems like the true right answer, but seems a bit much just to suppress a warning for a less than check.. :-) Anyhow, thanks again!

daroo
This should be a comment (you'd probably have to edit for size), probably on my answer. Avoid posting answers which really aren't answers. (And welcome to SO!)
Roger Pate
My solution actually does leave the check, but moves it to std::min, which takes care of not producing a warning in this case.
Roger Pate
Whoops - typical new user I guess, thanks for letting me know.. Roger: good point - what if I instead wanted the function to print a message if the val was less than 0, but not do any assigning?
daroo
Then do that, but it would probably be better to raise an exception in that case, or not even use a function at all (simpler and clearer to do `if (val < 0) { show_error_message(); do_whatever_else(); }` where you'd call this function).
Roger Pate
Don't forget to accept the answer you found most helpful! (Click the grey check to the left of the answer.)
Bill