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

sho*_*osh 6 c++ templates variadic-templates

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

#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...);    
}
Run Code Online (Sandbox Code Playgroud)

我可以打电话给:

log(std::cout, "Hello", 3);  
log(std::cout, "Goodbye", 4);
Run Code Online (Sandbox Code Playgroud)

我在Visual Studio 2013中使用所有优化(Release config)编译此代码,并使用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);
Run Code Online (Sandbox Code Playgroud)

但这是不可接受的,因为它会大大混乱代码.

Yak*_*ont 6

 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)...);    
 }
Run Code Online (Sandbox Code Playgroud)

在传递参数之前会手动衰减你的参数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)...);    
}
Run Code Online (Sandbox Code Playgroud)

这只会用于数组.

请注意,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)...
  };
}
Run Code Online (Sandbox Code Playgroud)

它在一个函数中的参数上进行直接扩展而不递归.您的编译器应该足够聪明,以确定隐含的0s 数组是无用的,即使不是,与io相比,这也是一个很小的开销.


Lig*_*ica 1

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

log(cout, +"Hello", 3);  
log(cout, +"Goodbye", 4);
Run Code Online (Sandbox Code Playgroud)

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