是否有可能使用模板元编程在编译时选择类将要执行的功能?

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

本文关键字:选择 执行 功能 编译 有可能 编程 是否      更新时间:2023-10-16

我经常这样写类:

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 (something)"部分在编译时。

编辑:bool变量的值在编译时已知。

引言

本文将提供两种解决方案,一种使用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");
}

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

使用可变模板的解决方案(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

是的,这是可能的,尽管有些编译器不喜欢您这样做。然而,你最终会得到一组不同的类,因为你必须提供布尔值作为模板说明符(可能不是正确的术语)。

我认为你最好使用虚拟日志方法代替?然后创建几个类,每个类定义自己的Log方法。除非你有其他理由,否则我建议在这种情况下使用虚函数而不是模板。

当然。像这样:

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>();那样调用

为什么要在不需要的地方使用模板呢?任何自重的c++编译器都会基于常量表达式进行常量折叠:无论如何,它必须在编译时计算出这些表达式的值。也就是说,任何基于常量表达式的条件都不会在运行时出现。这种方法仅有的两个缺点是:

  1. 你依赖于编译器在相当基本的水平上相当体面
  2. 从代码中引用的
  3. 符号从未执行过,我仍然被引用
但是,对于布尔标志,您仍然必须确保它们被识别为常量表达式。使用模板将强制执行这一点。

你可以这样做

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()将做定时器和百分比的东西你想

对于编译时间常数,您可以使用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,并分别记录它们。