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

时间:2012-02-19 05:06:32

标签: c++ templates c++11 metaprogramming

如果我的模板中包含许多其他代码。 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;
}

2 个答案:

答案 0 :(得分:4)

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

在linux上,您可以在生成的对象文件中使用readelf -s来转储公共符号,并将readelf -S转储到转储部分;每个非内联非静态函数将在符号表中具有(受损)条目。 AFAIK,模板实例在每个部分都有自己的部分,以便它们可以在链接时合并。

答案 1 :(得分:1)

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

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

但是你建议的优化基本上需要将内部范围与外部范围分开,这意味着将它们重构为新函数。如果不是临时的,你有一个命名变量,其生命周期包括内部switch,该怎么办?堆栈将重新排列,内部范围将取决于变量,尽管可能没有引用它,并且局部变量必须作为引用参数传递给switch。这将是一个脆弱,复杂的优化。

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

如果meta-bloat是另一个问题(我刚刚读到你正在使用代码生成器而且担心数千个这样的模板包装器的源代码大小),你可能会考虑改变一点接口:

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

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