4

Is there a way to give a default value for a template class?

E.g. Say I have a class TimeSeries, that is templated. And I have a function called Foo() that returns T.

template <typename T>
class TimeSeries
{
    T foo();
}

I want Foo() to do some calculation and return something of type T. But in case it can't, I want it to give a default value. If T was double, I want that to be NaN. If T was int, I want that to be 0.

TimeSeries<double> t;
t.foo(); // returns NAN

TimeSeries<int> t;
t.foo(); // returns 0

How would I achieve that?

A bad solution is for Foo() to take in a default value.

template <typename T>
class TimeSeries
{
    T foo(T defaultValue)
    {
       T ret;
       // if OK
       ret = computeValue();
       // if not OK
       ret = defaultValue;
       return ret;
    }
}

So default value would be 0, or NaN, depending on the user.

Or am I asking for a flawed design in the first place?

EDIT - A logical question would be to ask what happens if T is not int or double. What does a default value even mean?

The way I'm using TimeSeries, T is not exactly a general purpose type. It could only be 1 of int, double, and string. I thought about simply writing 3 separate TimeSeries classes, but that seems like a flawed approach.

I'm open to other ideas.

user3240688
  • 1,188
  • 3
  • 13
  • 34
  • It can be done in many ways. The simplest one is `if constexpr (std::is_integral_v) return 0; else return NAN;`. – HolyBlackCat Feb 05 '18 at 16:41
  • 1
    What would be the default value if `T` is something other than `double` or `int`? – R Sahu Feb 05 '18 at 16:47
  • I envision `TimeSeries` to not exactly be a general purpose class. The way I'm using it, it will only ever be `TimeSeries`, `TimeSeries`, and maybe `TimeSeries`. Of course I could have written 3 separate `TimeSeries` classes, but I felt like that'd be an even weirder solution. – user3240688 Feb 05 '18 at 17:16

3 Answers3

8

You could declare the following template, default_value<>:

template<typename>
struct default_value;

Then, provide full specializations for int and double with a member called value which provides the desired default value:

template<>
struct default_value<int> {
   static constexpr int value = 0;
};

template<>
struct default_value<double> {
   static constexpr double value = NaN;
};

Then, in your foo function template, you can simply return that value:

return default_value<T>::value;

which will depend on T.

JFMR
  • 23,265
  • 4
  • 52
  • 76
  • [`NaN` => `std::numeric_limits::quiet_NaN()`](https://stackoverflow.com/q/16691207/3893262). – O'Neil Feb 05 '18 at 16:55
4

since C++14 you can use a variable template:

template <class T> constexpr T default_value = {};
template <>
constexpr double default_value<double> = std::numeric_limits<double>::quiet_NaN();
template <typename T> class TimeSeries
{
    auto foo() -> T
    {
       T ret;

       if (condition)
         return ret;
       else
         return default_value<T>;
    }
};
bolov
  • 72,283
  • 15
  • 145
  • 224
2

Or am I asking for a flawed design in the first place?

As far as I'm concerned it's a valid requirement.

I see two ways you can address that requirment.

  1. You can use the default_value approach as suggested in the other answer.

  2. If you have the option of using C++17, you can use std::optional.

    If you don't have the option of using C++17, you can emulate std::optional in a less sophisticated way by changing the return value to std::pair<boo, T> with the understanding that the first of the returned value is set to false if the computation was not successful and to true if the computation was successful.

Personally, I would prefer the second solution. It does not rely on sentry values. It captures the state of whether the computation was successful in a clear and explicit manner.

Community
  • 1
  • 1
R Sahu
  • 204,454
  • 14
  • 159
  • 270