#include <iostream>
#include <iomanip>

//---------------------------------------------------------

template <typename TDerivedClass>
class AbstractLogger {
  public:
    void write(int x);
    void write(double x);
    
    template <typename T>
    void writeLine(T x);
};

template <typename TDerivedClass>
void AbstractLogger<TDerivedClass>::write(int x) {
    std::cout << x;
}

template <typename TDerivedClass>
void AbstractLogger<TDerivedClass>::write(double x) {
    std::cout << std::setprecision(3) << x;
}

template <typename TDerivedClass>
template <typename T>
void AbstractLogger<TDerivedClass>::writeLine(T x) {
    static_cast<TDerivedClass*>(this)->write(x);
    std::cout << std::endl;
}

//---------------------------------------------------------

class Logger : public AbstractLogger<Logger> {};

//---------------------------------------------------------

class FooType1 {};
class FooType2 {};

class FooLogger : public AbstractLogger<FooLogger> {
  public:
    using AbstractLogger::write;
    
    void write(FooType1 x);
    void write(FooType2 x);
};


void FooLogger::write(FooType1 x) {
    std::cout << "<value of type FooType1>";
}
void FooLogger::write(FooType2 x) {
    std::cout << "<value of type FooType2>";
}

//---------------------------------------------------------

int main(int argc, char **argv) {
    FooLogger log;
    FooType1 foo1;
    FooType2 foo2;
    
    log.write(42);           // OK
    log.write(3.14159);      // OK
    log.write(foo1);         // OK
    log.write(foo2);         // OK
    
    std::cout << std::endl;
    
    log.writeLine(42);       // OK
    log.writeLine(3.14159);  // OK
    log.writeLine(foo1);     // OK
    log.writeLine(foo2);     // OK
}