Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Avoid template instantiation for different char array sizes

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.

like image 273
shoosh Avatar asked Apr 02 '15 16:04

shoosh


People also ask

What is C++ class template array to instantiate an array?

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.

How to find the size of char datatype and char array?

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.

How to instantiate any type of array with single constructor?

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.

How to use templates in C++ with example?

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.


2 Answers

 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 0s is useless, and even if not that is little overhead compared to io.

like image 55
Yakk - Adam Nevraumont Avatar answered Sep 19 '22 10:09

Yakk - Adam Nevraumont


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.

like image 32
Lightness Races in Orbit Avatar answered Sep 19 '22 10:09

Lightness Races in Orbit