我有一个简单的可变参数模板代码,用于将参数写入流:
#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编译此代码并进行所有优化(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);
但这是不可接受的,因为它会大大混乱代码。
答案 0 :(得分: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)...);
}
会在传递参数之前手动衰减它们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
s的隐含数组是无用的,即使不是,与io相比,开销也很小。
答案 1 :(得分:1)
我没有为您提供适当的解决方案,但就解决方法而言,以下内容可能会更少,而且#34;混乱&#34;为你?
log(cout, +"Hello", 3);
log(cout, +"Goodbye", 4);
我感谢您仍然需要您的用户记住这样做,这很糟糕。