NoSenseEtAl
NoSenseEtAl

Reputation: 30028

Is it possible to use template metaprogramming to do compile time selection of functionality that class will perform?

Often I write classes like this:

Logger::Logger(bool log_time_, bool log_percentage, bool log_size):log_time(log_time_)... //made up example
       
Logger::Log()
{
    string log_line;
    if (log_time)
        log_line += (get_time());
    if (log_percentage)
        log_line += (get_percentage());
    //...
}

And I wonder is there a way to turn my class using template magic into a code that does the "if (something)" part at compile time.

EDIT: Values of bool variables are known at compile time.

Upvotes: 2

Views: 1656

Answers (6)

Dietmar Kühl
Dietmar Kühl

Reputation: 153840

Why would you want to employ templates where it isn't needed? Any self-respecting C++ compiler will do constant folding based on constant expressions: it has to work out the values for these at compile-time anyway. That is, any conditional based on a constant expression won't be there at run-time. The only two drawbacks of this approach are:

  1. you are relying on the compiler to be reasonably decent at a rather basic level
  2. symbols referenced from the code never executed my still be referenced

With respect to your Boolean flags you still have to make sure that they are recognized as constant expressions, however. Using a template would enforce this.

Upvotes: 1

Filip Roséen
Filip Roséen

Reputation: 63797

Preface

Two solutions will be found in this post, one using C++03 and the other C++11.

It's hard (ie. you'll need to write a lot of code) if you'd like a true compile time if that is guaranteed not to have any runtime overhead what so ever (no function jumps, etc etc).

It is however possible, though the code will be quite tedious to maintain if you feel like adding another option to it (in C++03). I'd recommend you to check out the below solutions.


Solution in C++03

Your compiler should be smart enough to optimize away any call to LogHelper<+NONE>, though if you are just looking for more readable code and not a superb performance gain this syntax is quite sweet.

enum LoggerType {
  NONE    =0,
  DATE    = (1<<0),
  TIME    = (1<<1),
  PERCENT = (1<<2)
};

template<int>     void LogHelper (std::string&);

template<> inline void LogHelper<+NONE>    (std::string&)   {}
template<> inline void LogHelper<+DATE>    (std::string& s) {s += "1970-01-01 ";}
template<> inline void LogHelper<+TIME>    (std::string& s) {s += "12:01:01 ";}
template<> inline void LogHelper<+PERCENT> (std::string& s) {s += "42% ";}

template<int LOG_FLAG = NONE>
struct Logger {
  static void log (std::string const& description) {
    std::string s1;

    LogHelper<DATE    & LOG_FLAG> (s1);
    LogHelper<TIME    & LOG_FLAG> (s1);
    LogHelper<PERCENT & LOG_FLAG> (s1);

    std::cerr.width (25);
    std::cerr << s1 << " >> " << description << std::endl;
  }
};

...

int
main (int argc, char * argv[]) {
  Logger<DATE|TIME|PERCENT> foo_log;
  Logger<TIME>             time_log;
  Logger<>                   no_log;

  time_log.log ("log objects initialized!");
  foo_log .log ("using foo_log");
  no_log  .log ("about to terminate application");
}

output

                12:01:01  >> log objects initialized!
 1970-01-01 12:01:01 42%  >> using foo_log
                          >> about to terminate application

Solution using Variadic Templates (C++11)

enum LoggerType {
  NONE, PERCENT, DATE, TIME
};

template<LoggerType T = NONE, LoggerType ... Next>
std::string LogHelper () {
  return LogHelper<T> () + "; " + LogHelper<Next...> ();
}

template<> std::string LogHelper<NONE>    () {return ""; }
template<> std::string LogHelper<DATE>    () {return "1970-01-01";}
template<> std::string LogHelper<TIME>    () {return "00:01:42";}
template<> std::string LogHelper<PERCENT> () {return "42%";}

template<LoggerType ... Types>
struct Logger {
  static void log (std::string const& description) {
    std::cerr.width (25);
    std::cerr << LogHelper<Types...> ();
    std::cerr << " >> "  <<   description;
    std::cerr << std::endl;
  }
};

...

int
main (int argc, char * argv[]) {
  Logger<DATE,TIME,PERCENT> foo_log;
  Logger<TIME>             time_log;
  Logger<>                   no_log;

  time_log.log ("log objects initialized!");
  foo_log .log ("using foo_log");
  no_log  .log ("about to terminate application");
}

output

                 00:01:42 >> log objects initialized!
1970-01-01; 00:01:42; 42% >> using foo_log
                          >> about to terminate application

Upvotes: 3

iammilind
iammilind

Reputation: 69988

Yes for compile time constants you can use template programming:

template<bool log_time, bool log_perchentage, bool log_size>
struct Logger
{
  static void log()
  {  // log everything
    string log_line;
    log_line+=(get_time());
    log_line+=(get_perchentage());
    log_line+=(get_size());
  }
};

template<>
struct Logger<false, false, false>
{
  static void log()
  {  // nothing to log
  }
};

You can also specialize the intermediate versions as Logger<true, false, false> and Logger<false, true, true> and so on. The other way to avoid several specializations is to separate time / percentage / size into different structs and log them separately.

Upvotes: 0

parapura rajkumar
parapura rajkumar

Reputation: 24403

You can do something like this

struct DummyEnhancer 
{
    void operator()(string& s) const{
    }
};

struct TimerEnhancer
{
    void operator()(string& s) const{
        s += "time";
    }
};

struct PercenterEnhancer
{
    void operator()(string& s) const{
        s += "percent";
    }
};

template <typename Timer , typename Percenter>
struct Logger
{
    void Log()
    {
        string log_line;

        Timer t;
        t( log_line );

        Percenter p;
        p( log_line );
    }
};

int main()
{

    Logger<DummyEnhancer,DummyEnhancer> foo;
    foo.Log();

    Logger< TimerEnhancer , PercenterEnhancer > bar;
    bar.Log();

    return 0;
}

foo.Log() will be a no op and bar.log() will do both the timer and percentage stuff you want

Upvotes: 0

Kerrek SB
Kerrek SB

Reputation: 477060

Sure. Something like this:

template <bool Opt1, bool Opt2> void foo()
{
     Action1<Opt1>();
     Action2<Opt2>();
}

template <bool> void Action1();
template <bool> void Action2();

template <> void Action1<true>()  { /* ... */ }
template <> void Action1<false>() { /* ... */ }
template <> void Action2<true>()  { /* ... */ }
template <> void Action2<false>() { /* ... */ }

Invoke this like foo<true, false>();.

Upvotes: 1

noggin182
noggin182

Reputation: 637

Yes this is possible, although some compilers will not like for you it. You will essentially however end up with a set of different classes as you have to provide booleans as the template specifiers (may not be the correct terminology).

I think you maybe better off using a virtual Log method instead? Then create a handful of classes which each define their own Log method. Unless you have some other reason I would suggest using virtual functions over templates for this case.

Upvotes: 1

Related Questions