【问题标题】:Avoid template instantiation for different char array sizes避免针对不同的 char 数组大小进行模板实例化
【发布时间】:2015-04-03 02:08:07
【问题描述】:

我有一个简单的可变参数模板代码来将参数写入流:

#include <iostream>

void tostream(std::ostream& os) {
}

template<typename T, typename... Args>
void tostream(std::ostream& os, const T& v, const Args&... args) {
    os << v;
    tostream(os, args...);
}

template<typename... Args>
void log(std::ostream& os, const Args&... args) {
    tostream(os, args...);    
}

我可以打电话给:

log(std::cout, "Hello", 3);  
log(std::cout, "Goodbye", 4);

我使用 Visual Studio 2013 编译此代码,并在(发布配置)上进行所有优化,并使用 IDA Disassembler 打开生成的可执行文件。
我看到的是编译器实例化了log() 函数的两个副本。一种采用const char[6], int,另一种采用const char[8], int
当单步执行这些函数并观察调用堆栈窗口时,这在调试器中也很明显。
除了签名之外,这两个函数是相同的。

有没有办法让编译器相信这两个函数实际上应该是一个接受const char*, int 而不是两个函数的函数?

这对我来说是个问题,因为我有数百个这样的函数实例使我的可执行文件膨胀,其中大部分是可以避免的。
对于不同的参数组合,该函数仍然会有很多实例化,但它们会少很多,因为我只有很少的可能的参数组合。

可以解决这个问题的方法是像这样调用函数:

log(cout, (const char*)"Hello", 3);  
log(cout, (const char*)"Goodbye", 4);

但这是不可接受的,因为它会使代码非常混乱。

【问题讨论】:

  • 不是一个真正的答案,但您是否尝试暗示编译器它可能想要inline 函数调用?或者甚至强迫它?
  • 你已经在这里六年,但你还在为所有的标题写标签吗? :sadface:
  • @shoosh:你有没有发现几乎没有其他人这样做,并且当他们这样做时,标题会被编辑?我们已经有一个标签系统,与您的标题前缀不同,它是语义和索引的。
  • @LightningRacisinObrit 我很乐意,以防这实际上是全球强制的风格指南,而不仅仅是您的个人喜好。
  • Should questions include tags in titles? -- 对于 comdat,递归通过限制可能会限制清除重复函数的能力。在第一次通过时,&lt;&lt; 被弃牌。在第二次通过时,tostream 被弃牌。 log 我猜如果你添加另一个 comdat 折叠通行证会被淘汰?

标签: c++ templates variadic-templates


【解决方案1】:
 template<class T>
 using decay_t = typename std::decay<T>::type;

 template<typename... Args>
 void log(ostream& os, const Args&... args) {
   tostream(os, decay_t<Args>(args)...);    
 }

将在传递参数之前手动衰减你的参数tostream。这会将函数转换为函数指针,将数组引用转换为指针等。

这可能会导致一些虚假副本。对于原始类型,没问题,但对于std::string 等来说是浪费的。所以一个更窄的解决方案:

template<class T>
struct array_to_ptr {
  using type=T;
};
template<class T, size_t N>
struct array_to_ptr<T(&)[N]> {
  using type=T*;
};
template<class T>
using array_to_ptr_t=typename array_to_ptr<T>::type;

template<typename... Args>
void log(ostream& os, const Args&... args) {
  tostream(os, array_to_ptr_t<Args const&>(args)...);    
}

只对数组执行此操作。

请注意,log 的不同实现可能仍然存在,但 tostream 不存在。 log 的不同实现应通过 comdat 折叠和/或 log 的内联来消除,并且可能消除递归要求(注意它是可折叠的)将使其更容易。

最后,这可能有用:

template<typename... Args>
void tostream(std::ostream& os, const Args&... args) {
  using expand=int[];
  (void)expand{0,
    ((os << args),void(),0)...
  };
}

直接展开而不递归一个函数中的参数。你的编译器应该足够聪明,可以判断出0s 的隐含数组是无用的,即使不是这样,与 io 相比,开销也很小。

【讨论】:

  • 如果我们想消除额外的模板实例化,一个好的选择是使用单个包扩展而不是递归?
  • @T.C. 点头。添加了 int[] 扩展。可惜我们不是 C++1z,折叠表达式很好吃。
【解决方案2】:

我没有适合您的解决方案,但就解决方法而言,以下内容对您来说是否足够“混乱”?

log(cout, +"Hello", 3);  
log(cout, +"Goodbye", 4);

我很感激您仍然需要您的用户记住这样做,这很糟糕。

【讨论】:

    猜你喜欢
    • 2014-10-01
    • 1970-01-01
    • 1970-01-01
    • 2020-10-13
    • 1970-01-01
    • 1970-01-01
    • 2019-11-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多