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

x9ybnkn6  于 2023-02-06  发布在  其他
关注(0)|答案(6)|浏览(139)

我经常这样写类:

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变量的值在编译时是已知的。

cwtwac6a

cwtwac6a1#

前言

本文将介绍两种解决方案,一种使用C 03,另一种使用C11。
如果你想要一个真正的编译时间,这是很难的(也就是说,你需要写很多代码),如果这是保证**没有任何运行时开销,所以以往任何时候(没有函数跳转等)。
不过这是可能的,尽管如果你想添加另一个选项(在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
zdwk9cvp

zdwk9cvp2#

是的,这是可能的,尽管有些编译器可能不喜欢这样做,但实际上你最终会得到一组不同的类,因为你必须提供布尔值作为模板说明符(可能不是正确的术语)。
我想你最好使用一个虚拟的Log方法,然后创建一些类,每个类定义自己的Log方法。除非你有其他原因,我建议在这种情况下使用虚拟函数而不是模板。

2skhul33

2skhul333#

当然。就像这样:

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>();一样调用它。

ffdz8vbo

ffdz8vbo4#

为什么要在不需要模板的地方使用模板呢?任何有自尊心的C++编译器都会基于常量表达式进行常量折叠:无论如何,它必须在编译时计算出这些值。也就是说,任何基于常量表达式的条件在运行时都不会存在。这种方法仅有的两个缺点是:
1.您依赖于编译器在相当基本的级别上相当不错
1.从代码中引用的符号从未执行,但仍将被引用
至于布尔标志,你仍然需要确保它们被识别为常量表达式,但是使用模板会强制执行这一点。

7xzttuei

7xzttuei5#

你可以做这样的事

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()将为no op,bar.log()将执行您想要的计时器和百分比操作

46qrfjad

46qrfjad6#

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

相关问题