避免针对不同的字符数组大小进行模板实例化

Avoid template instantiation for different char array sizes

本文关键字:实例化 数组 字符      更新时间:2023-10-16

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

#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 反汇编程序打开生成的可执行文件。
我看到的是编译器实例化了log()函数的两个副本。一个需要const char[6], int,一个需要const char[8], int.
在单步执行这些函数并监视调用堆栈窗口时,这在调试器中也很明显。
这两个函数除了签名外是相同的。

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

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

解决这个问题的一件事是像这样调用函数:

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

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

 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)...
  };
}

它执行直接扩展,而无需递归一个函数中的参数。 你的编译器应该足够聪明,可以弄清楚0的隐含数组是无用的,即使不是,与io相比,这也是很小的开销。

我没有

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

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

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