C++ Overloaded operator for a class with traits

Viewed 272

I'm getting to know traits in C++. I've got these classes:

class Basic_traits {
public:
    ...
    static const int prec = 0;
};


template<typename T>
class Traits : public Basic_traits {
public:
    ...
};


class Temperature {
    double t;
public:
    Temperature(double temp = 50) : t(temp) {};
    double operator()() { return t; };
    Temperature& operator=(double temp) { t = temp; return *this; };
};


template<>
class Traits<Temperature> : public Basic_traits{
public:
    ...
    static const int prec = 2;
    friend ostream& operator<<(ostream& os, const Temperature&) {
        os.precision(prec);
    }
};

What I want to do here is make an overloaded << operator for class Temperature, which will cout the t variable with the precision given in the prec trait. Is this even possible? If so, then how do i do it? Is the operator supposed to be in the template? I put it there cause that's where the prec is and class Temperature itself doesn't have anything to do with Basic_traits which contains a variable prec.

////////////////////////////////////////////////////////////////////////////////

My operator now looks like this:

friend ostream& operator<<(ostream& os, const Temperature&) {
    os.precision(Traits<Temperature>::prec);
    return os << (Temperature);
}
2 Answers

Here is an example which uses a base class template Measurement which defined the ostream operator, and uses Traits<T> to set the precision of the value it is outputting.

template<typename T>
struct Measurement
{
    double val;
    friend std::ostream& operator<<(std::ostream& os, const Measurement<T>& measurement)
    {
        return os << std::setprecision(Traits<T>::prec) << measurement.val;
    }
};

We can then have different types of measurements, eg Temperature:

struct Temperature : Measurement<Temperature>
{
    Temperature(double v)
    {
        val = v;
    }
};

We can then specialise Traits for Temperature

template<>
struct Traits<Temperature>
{
    static const int prec = 2;
};

Now when you output anything which is a Measurement, it will use the specific traits for the derived class.

Here is a full example:

#include <iostream>
#include <iomanip>

template<typename T>
struct Traits
{
    static const int prec = 0;
};

// our base class "Measurement"
template<typename T>
struct Measurement
{
    double val;
    friend std::ostream& operator<<(std::ostream& os, const Measurement<T>& measurement)
    {
        return os << std::setprecision(Traits<T>::prec) << measurement.val;
    }
};

// a specific measurement type, Temperature
struct Temperature : Measurement<Temperature>
{
    Temperature(double v)
    {
        val = v;
    }
};

// specialise Traits for Temperature
template<>
struct Traits<Temperature>
{
    static const int prec = 2;
};

// a specific measurement type, Distance
struct Distance : Measurement<Distance>
{
    Distance(double v)
    {
        val = v;
    }
};

// specialise Traits for Distance
template<>
struct Traits<Distance>
{
    static const int prec = 4;
};

int main()
{
    Temperature temp { 5.2134 };
    Distance dist { 5.2134 };

    std::cout << temp << '\n';
    std::cout << dist << '\n';

    return 0;
}

For your particular example code I would suggest you typically wouldn't use a traits class, but instead just setprecision(2) inside the ostream operator for Temperature.

However, as you have mentioned you are more interested in getting to know traits, below is a somewhat contrived example showing a Traits primary class template, and 2 specialisations of that to alter the traits.

Start off by defining your primary Traits class template. Any type for which specialisation of Traits doesn't exist will use this.

template<typename T>
struct Traits
{
    static const int prec = 0;
};

We then specialise Traits for our various types. Here I do so for Temperature and Distance.

template<>
struct Traits<Temperature>
{
    static const int prec = 2;
};

template<>
struct Traits<Distance>
{
    static const int prec = 4;
};

We then use Traits in a generic setting, and select the appropriate traits for the provided type. Here I have a generic function print which will set the precision based on that provided by Traits

template<typename T>
void print(const T& t)
{
    std::cout << std::setprecision(Traits<T>::prec) << t << '\n';
}

Here is an example tying it all together:

#include <iostream>
#include <iomanip>

template<typename T>
struct Traits
{
    static const int prec = 0;
};

struct Temperature
{
    double t;
    friend std::ostream& operator<<(std::ostream& os, const Temperature& t)
    {
        return os << t.t;
    }
};

// specialise Traits for Temperature
template<>
struct Traits<Temperature>
{
    static const int prec = 2;
};

struct Distance
{
    double d;
    friend std::ostream& operator<<(std::ostream& os, const Distance& d)
    {
        return os << d.d;
    }
};

// specialise Traits for Distance
template<>
struct Traits<Distance>
{
    static const int prec = 4;
};

template<typename T>
void print(const T& t)
{
    std::cout << std::setprecision(Traits<T>::prec) << t << '\n';
}

int main()
{
    Temperature temp { 5.2134 };
    Distance dist { 5.2134 };
    double d { 5.2134 };

    print(temp); // will use precision=2
    print(dist); // will use precision=4
    print(d);    // will use the fallback precision=0

    return 0;
}

Output:

$ ./a.out
5.2
5.213
5
Related