避免针对不同的字符数组大小进行模板实例化
Avoid template instantiation for different char array sizes
我有一个简单的可变参数模板代码来将参数写入流:
#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);
我很感激你仍然需要你的用户记住这样做,这很糟糕。
- 是否可以在编译时初始化数组,以便在运行时不会花费时间?
- 在函数内部的声明中初始化数组,并在外部使用它
- 在调用接收数组的方法时,模板化数组大小是不是一种糟糕的做法
- 为什么用结构初始化数组需要指定结构名称
- 有没有一种代码密度较低的方法来使用非默认构造函数初始化数组?
- C++使用另一个数组和新值初始化数组
- 初始化数组、"memset"或" {//value} "的最佳方法是什么?
- 如何反序列化数组?
- 从函数中全局删除并重新实例化数组结构,而无需在编译时知道数组的大小
- 在 constexpr 构造函数中初始化数组是否合法?
- 我可以初始化 const 实例,以便我可以将其用作 const 来初始化数组吗?
- 在构造函数中初始化数组
- 是否可以使用函数返回的值初始化数组
- 使用宏使用额外元素初始化数组
- 在循环中显示不同值的初始化数组
- 如何为包含另一个类实例的数组制作常量 getter?
- 我可以从initializer_list实例化数组吗?
- 实例化数组的方法之间的区别
- 关于使用glBufferSubData频繁更新实例化数组的困惑
- C++对象实例化数组(我正在尝试找到编译时解决方案)