Можно ли использовать шаблонное метапрограммирование для выбора времени компиляции функций, которые будет выполнять класс? - PullRequest
1 голос
/ 03 января 2012

Часто я пишу такие классы:

    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());
        //...
    }

И мне интересно, есть ли способ превратить мой класс с использованием магии шаблонов в код, который выполняет часть if (что-то) во время компиляции.

EDIT: Значения переменных bool известны во время компиляции.

Ответы [ 6 ]

3 голосов
/ 03 января 2012

Предисловие

В этом посте будут найдены два решения, одно с использованием C ++ 03, а другое C ++ 11.

Это сложно (т.е. вам нужно написатьмного кода), если вы хотите истинное время компиляции , если , что гарантировано , не будет никаких накладных расходов во время выполнения, как раньше (без скачков функций и т. д.).

Однако это возможно, хотя поддерживать код будет довольно утомительно, если вы захотите добавить к нему другую опцию (в C ++ 03).Я бы порекомендовал вам проверить следующие решения.


Решение на C ++ 03

Ваш компилятор должен быть достаточно умен, чтобы оптимизировать любой вызов на LogHelper<+NONE>, хотяесли вы просто ищете более читаемый код и не получаете прирост производительности, этот синтаксис довольно приятный.

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

Решение с использованием шаблонов Variadic (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");
}

вывод

                 00:01:42 >> log objects initialized!
1970-01-01; 00:01:42; 42% >> using foo_log
                          >> about to terminate application
1 голос
/ 04 января 2012

Зачем вам использовать шаблоны там, где они не нужны? Любой уважающий себя компилятор C ++ будет делать константное сворачивание на основе константных выражений: ему все равно придется вычислять их значения во время компиляции. То есть, любое условие, основанное на константном выражении, во время выполнения не будет. Единственными двумя недостатками этого подхода являются:

  1. вы полагаетесь на то, что компилятор будет достаточно приличным на довольно базовом уровне
  2. символы, на которые ссылается код, никогда не выполнялись, но на меня все еще будут ссылаться

Что касается ваших логических флагов, вам все равно нужно убедиться, что они распознаются как константные выражения. Использование шаблона приведет в исполнение это.

1 голос
/ 03 января 2012

Да, это возможно, хотя некоторые компиляторы вам не понравятся.Тем не менее, в конечном итоге вы получите набор различных классов, поскольку вы должны указывать логические значения в качестве спецификаторов шаблона (возможно, это неправильная терминология).

Я думаю, что вам лучше использовать метод виртуального журнала вместо этого?Затем создайте несколько классов, каждый из которых определяет свой собственный метод Log.Если у вас нет другой причины, я бы предложил использовать виртуальные функции над шаблонами для этого случая.

0 голосов
/ 03 января 2012

Вы можете сделать что-то вроде этого

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() будет опцией без операции, а bar.log() выполнит желаемую работу по таймеру и процентам

0 голосов
/ 03 января 2012

Да для констант времени компиляции вы можете использовать template программирование:

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
  }
};

Вы также можете специализировать промежуточные версии как Logger<true, false, false> и Logger<false, true, true> и так далее. Другой способ избежать нескольких специализаций - разделить time / percentage / size на разные struct s и зарегистрировать их отдельно.

0 голосов
/ 03 января 2012

Конечно. Примерно так:

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>() { /* ... */ }

Вызовите это как foo<true, false>();.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...