g ++模板实例化器有多聪明(避免模板膨胀)

问题描述:

如果我有一个包含一堆其他代码的模板。 g ++是否会重新生成对于每个版本的模板都相同的所有代码?g ++模板实例化器有多聪明(避免模板膨胀)

例如:

template <typename> T 
T parseSomething(const std::string& data) { 
    // Some state variables go here 
    enum State {state1,state2,state3} state; 
    for(std::string::const_iterator i=data.begin();i!=data.end();++i) { 
     // Some big testy stuff to see if we got in the right place 
     switch (state) { 
      case state1: { 
       switch (*i) { 
        case f: // ... 
     // ... lots of switchy stuff here .. 
     return T(*i); 
    } 
} 

所以在这FUNC ..真正需要的模板的唯一位是返回T(* I)线。

假设我用4个不同的Ts例化它。

parseSomething<float>(data); 
parseSomething<int>(data); 

会克++生成所有其它代码(环和开关部件),用于每T单独的时间?或者它会足够聪明,只生成开关和循环一次..然后为每个T ..生成返回T(* i);线?

我尝试过使用-O0进行测试,但它确实复制了各处的开关,但是-O2和以上版本很难说;它似乎是聪明..但它是如此聪明,我无法破译ASM :)


这是我想要使用来测试我的示例程序。

编译:

g++ -std=c++0x -fverbose-asm -ggdb3 -fvar-tracking-assignments -O6 -march=native codegen.cpp 

运行:

gdb --args ./a.out asdf1111 

偏执代码版本:

#include <iostream> 
#include <string> 

using namespace std; 

char getSomething(const string& myString) { 
    for(auto myPlase=myString.begin();myPlase!=myString.end();++myPlase) { 
     if (*myPlase == 'f') { 
      return *(myPlase+1); 
     } 
    } 
} 

template <typename T> 
T getSomething(const string& myString) { 
    return T(getSomething(myString)); 
} 

int main(int argc, char** argv) { 
    string base = argv[1]; 
    float myFloat = getSomething<float>(base); 
    int myInt = getSomething<int>(base); 
    char myChar = getSomething<char>(base); 
    //string newString = getSomething<string>(base); 
    cout << myFloat << " " << myInt << " " << myChar << endl; 
} 

代码版本我想使用:

#include <iostream> 
#include <string> 

using namespace std; 

template <typename T> 
T getSomething(const string& myString) { 
    for(auto myPlace=myString.begin();myPlace!=myString.end();++myPlace) { 
     if (*myPlace == 'f') { 
      return T(*(myPlace+1)); 
     } 
    } 
} 

int main(int argc, char** argv) { 
    string base = argv[1]; 
    float myFloat = getSomething<float>(base); 
    int myInt = getSomething<int>(base); 
    char myChar = getSomething<char>(base); 
    //string newString = getSomething<string>(base); 
    cout << myFloat << " " << myInt << " " << myChar << endl; 
} 
+0

在现实世界中,我使用ragel生成代码,它大约有1200行。我想将它变成一个模板函数,主模板位是返回类型,但我不希望看到为每种类型重新生成这些1200行。 – matiu 2012-02-19 05:17:31

+1

如果你是GCC,你将如何生成有问题的代码? – 2012-02-19 06:31:35

+1

当然真正的问题是:在偏执的代码示例中,调用“getsomething”内联:)? – 2012-02-19 11:04:02

我不认为编译器足够聪明来合并独立于模板参数的代码。换句话说,该函数将被实例化4次,每个T一次。

在linux上,您可以在生成的目标文件上使用readelf -s来转储公共符号,并使用readelf -S来转储节;每个非内联非静态函数在符号表中都有一个(错位)条目。 AFAIK,模板instatiations每个都在它自己的部分,以便他们可以在链接时合并。

+0

谢谢@zvrba ..是啊,麻烦是我打开任何东西> = -O1它将整个内容都包含进来,而且ASM看起来非常复杂,所以我不能真正分辨出它是否取出了循环,切换东西。我倾向于它可能不会把它拿出来;并与我认为偏执狂的代码。 – matiu 2012-02-19 07:34:44

在模板内部有一大块非参数化代码是不常见的,无论它是一个类还是一个函数。

检测不依赖参数的大块代码听起来并不困难。引擎只需要在相关的AST节点中记录一些子树大小的度量,以及是否有任何子节点是模板参数。

但是,您建议的优化本质上需要将内部作用域与外部作用域分开,这意味着将其重构为新函数。如果不是临时的,你有一个命名变量,其生命期包括内部switch?堆栈将被重新安排,内部作用域将依赖于变量,尽管可能没有引用它,局部变量将不得不作为参考参数传递给switch。这将是一个脆弱,复杂的优化。

如果模板膨胀是一个问题,我会认真推荐“偏执狂”版本,它将模板关注分离为包装函数。这样的包装不应该很复杂,因为重点是避免膨胀!

如果荟萃膨胀是一个问题(我刚刚看了你正在使用一个代码生成和担心的数千个这样的模板包装的源代码大小),你可能会考虑改变界面一点:

template< typename T, char (*func)(std::string const &) > 
T get_anything(std::string const &s) { 
    return T(func(s)); 
} 

这样,可以有很多get_something()函数,它们都可以用作get_anything的第二个模板参数。您也可以使用指向成员的指针而不是函数指针作为模板参数。