I have a simple variadic template code to write argument to a stream:
#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...);
}
which I can call with:
log(std::cout, "Hello", 3);
log(std::cout, "Goodbye", 4);
I compile this code with Visual Studio 2013 with all optimizations on (Release config) and open the resulting executable with IDA Disassembler.
What I see is that the compiler instantiated two copies of the log()
function. One that takes const char[6], int
and one that takes const char[8], int
.
This is also apparent in the debugger when stepping into these functions and watching the call-stack window.
The two functions are identical other than their signature.
Is there a way to convince the compiler that these two functions should actually be one function which takes const char*, int
and not two functions?
This is an issue for me since I have hundreds of these function instantiations bloating the size of my executable, most of which could have been avoided.
There will still be many instantiations to the function, for the different combinations of arguments, but there would be a lot less of them since I only have very few possible argument combinations.
One thing that would solve this would be to call the function like so:
log(cout, (const char*)"Hello", 3);
log(cout, (const char*)"Goodbye", 4);
But this is unacceptable since it would clutter the code greatly.
Example of C++ class template Array to instantiate an Array of any element type. This small C++ example program demonstrates an usage of templates within c++. In this example "template class array" can instantiate any type of arrays with single constructor and single setArray member function.
size of char datatype and char array in C. 1 first, the char variable is defined in charType and the char array in arr. 2 Then, the size of the char variable is calculated using sizeof () operator. 3 Then the size of the char array is find by dividing the size of the complete array by the size of the first variable.
In this example “template class array” can instantiate any type of arrays with single constructor and single setArray member function. Such a behavior can also be done by overloading a constructors and setArray member function.
This small C++ example program demonstrates an usage of templates within c++. In this example “template class array” can instantiate any type of arrays with single constructor and single setArray member function. Such a behavior can also be done by overloading a constructors and setArray member function.
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)...);
}
will manually decay your arguments before passing them tostream
. This will convert functions to function pointers, array references into pointers, etc.
This could cause some spurious copies. For primitive types, no problem, but is wasteful for std::string
and the like. So a narrower solution:
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)...);
}
which will only do it for arrays.
Note that distinct implementations of log
may still exist, but not of tostream
. The distinct implementations of log
should be eliminated by comdat folding and/or inlining of log
, and possibly eliminating the recursion requirement (to notice it is foldable) will make it easier.
Finally, this may be useful:
template<typename... Args>
void tostream(std::ostream& os, const Args&... args) {
using expand=int[];
(void)expand{0,
((os << args),void(),0)...
};
}
which does a direct expansion without recursion on the arguments in one function. Your compiler should be smart enough to figure out that the implied array of 0
s is useless, and even if not that is little overhead compared to io.
I don't have a proper solution for you but, in terms of workarounds, would the following be sufficiently less "clutter" for you?
log(cout, +"Hello", 3);
log(cout, +"Goodbye", 4);
I appreciate that you still need your users to remember to do this, which sucks.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With