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

How smart is the g++ template instantiator (avoiding template bloat)

本文关键字:膨胀 实例化 g++      更新时间:2023-10-16

如果我有一个模板,里面有很多其他代码。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);
    }
}

所以在这个函数中。。唯一真正需要模板的是返回T(*i)行。

假设我用4个不同的Ts实例化它,例如

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

等等。

g++会为每个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;
}

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

在linux上,可以在生成的对象文件上使用readelf -s来转储公共符号,使用readelf -S来转储节;每个非内联的非静态函数在符号表中都有一个(损坏的)条目。AFAIK,每个模板安装都有自己的部分,这样它们就可以在链接时合并。

无论是类还是函数,在模板中有一大块非参数化代码都是非常罕见的。

检测一大块不依赖于参数的代码听起来并不困难。引擎只需要记录相关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的第二个模板参数。您也可以使用指向成员的指针而不是函数指针作为模板参数。