prestokeys
prestokeys

Reputation: 4839

Virtual functions with differences here and there

Consider this output:

Current time: 6:30 pm
Current time: 18:30
Current time: evening.
Current time: evening (for many it is dinner time, but many eat dinner later).

Note that the last two have a period, while the first two do not. I obtained this desired output with the System::displayCurrentTime member function from the code below:

#include <iostream>
#include <string>
#include <memory>

class TimeDisplay {
    public:
        virtual std::string tell() const = 0;
        virtual std::string tellMaybeWithPeriod() const = 0;
};

class ClockDisplay12Hours : public TimeDisplay {  // #1
    std::string tell() const override {return "6:30 pm";}
    std::string tellMaybeWithPeriod() const override {return tell();}
};

class ClockDisplay24Hours : public TimeDisplay {  // #2
    std::string tell() const override {return "18:30";}
    std::string tellMaybeWithPeriod() const override {return tell();}
};

class DescriptiveTimeDisplay : public TimeDisplay {  // #3
    std::string tell() const override {return "evening";}
    std::string tellMaybeWithPeriod() const override {return tell() + ".";}
};

class CrazyDescriptiveTimeDisplay : public TimeDisplay {  // #4
    std::string tell() const override {return "evening (for many it is dinner time, but many eat dinner later)";}
    std::string tellMaybeWithPeriod() const override {return tell() + ".";}
};

struct System {
    static std::shared_ptr<TimeDisplay> timeDisplay;
    static std::string timeAsString() {return timeDisplay->tell();}
    static std::string timeAsStringMaybeWithPeriod() {return timeDisplay->tellMaybeWithPeriod();}
    // #3 and #4 will have a period, the others will not.
    static void displayCurrentTime (std::shared_ptr<TimeDisplay> t) {
        timeDisplay = t;
        std::cout << "Current time: " << System::timeAsStringMaybeWithPeriod() << '\n';
    }
    static void foo (std::shared_ptr<TimeDisplay>) {}  // #1 and #3 will have a period, the others will not.
    static void bar (std::shared_ptr<TimeDisplay>) {}  // #1, #2, and #4 will have a period, the others will not.
    static void baz (std::shared_ptr<TimeDisplay>) {}  // #2 will have a period, the others will not
};
std::shared_ptr<TimeDisplay> System::timeDisplay;

int main() {
    const std::shared_ptr<TimeDisplay> clocks[] = {std::make_shared<ClockDisplay12Hours>(), std::make_shared<ClockDisplay24Hours>(),
        std::make_shared<DescriptiveTimeDisplay>(), std::make_shared<CrazyDescriptiveTimeDisplay>()};
    for (std::shared_ptr<TimeDisplay> t : clocks)
        System::displayCurrentTime(t);
}

This is not terribly messy, but not note that the next functions to implement foo, bar, baz want the periods with different derived classes of TimeDisplay, and there are actually much more than 4 such derived classes, and also more than 3 new member functions to take care of. Is there a cleaner more elegant way to handle these upcoming member functions than to write out new virtual functions for each of foo, bar, baz, etc... along with which will get the period and which will not? Use templates somehow (e.g. renaming the derived classes Derived<0>, Derived<1>, etc... and then using these compile-time integers to fit the rules stated in the comments above)? Of maybe avoid templates and do something else?

Upvotes: 0

Views: 61

Answers (2)

prestokeys
prestokeys

Reputation: 4839

Thanks to simon's idea, I now got the ideal solution I wanted using the following traits:

template <int, int> struct PeriodOrNoPeriod;

template <> struct PeriodOrNoPeriod<0,0> : std::false_type {};
template <> struct PeriodOrNoPeriod<0,1> : std::false_type {};
template <> struct PeriodOrNoPeriod<0,2> : std::true_type {};
template <> struct PeriodOrNoPeriod<0,3> : std::true_type {};

template <> struct PeriodOrNoPeriod<1,0> : std::false_type {};
template <> struct PeriodOrNoPeriod<1,1> : std::true_type {};
template <> struct PeriodOrNoPeriod<1,2> : std::true_type {};
template <> struct PeriodOrNoPeriod<1,3> : std::false_type {};

template <> struct PeriodOrNoPeriod<2,0> : std::true_type {};
template <> struct PeriodOrNoPeriod<2,1> : std::true_type {};
template <> struct PeriodOrNoPeriod<2,2> : std::false_type {};
template <> struct PeriodOrNoPeriod<2,3> : std::true_type {};

template <> struct PeriodOrNoPeriod<3,0> : std::false_type {};
template <> struct PeriodOrNoPeriod<3,1> : std::true_type {};
template <> struct PeriodOrNoPeriod<3,2> : std::false_type {};
template <> struct PeriodOrNoPeriod<3,3> : std::false_type {};

and the function to handle all cases at once:

template <System::Action A, int N>
struct System::SetTimeDisplay {
    static void execute (TimeDisplay::Mode mode) {
        constexpr TimeDisplay::Mode M = static_cast<TimeDisplay::Mode>(N);
        if (mode == M)
            timeDisplay = std::make_unique<TimeDisplayClass<M, PeriodOrNoPeriod<A,M>::value>>();
        else
            SetTimeDisplay<A, N+1>::execute(mode);
    }
};

template <System::Action A>
struct System::SetTimeDisplay<A, TimeDisplay::NumTimeDiplayModes> {
    static void execute (TimeDisplay::Mode) {}  // End of recursion
};

template <System::Action A>
inline void System::action (TimeDisplay::Mode mode) {
    SetTimeDisplay<A,0>::execute(mode);
    finalAction<A>();
}

Full solution here:

http://ideone.com/6pET9E

Upvotes: 0

simon
simon

Reputation: 391

It can be done by giving a bool template parameter to the four clock display classes. e.g.

template <bool P>
class DescriptiveTimeDisplay : public TimeDisplay {  // #3
    std::string tell() const override { return "evening"; }
    std::string tellMaybeWithPeriod() const override { return tell() + (P ? "." : ""); }
};

and control whether the period will be displayed by intantiate the class as, e.g.

std::make_shared<DescriptiveTimeDisplay<true>>()

for each of the four functions displayCurrentTime, foo, bar, baz, you can control their display format by instantiating the four TimeDisplay child classes with different bool template parameters.

Upvotes: 1

Related Questions