Thursday 18 February 2016

Explicit template instantiation - Exhibit 1

As I promised last time, let's put our design to work on some code.

Back in 2013, I wrote a couple of template functions as a quick solution to a very specific problem - outputting non-ASCII characters on a Windows console in a program compiled with Mingw. Here's the header, and here's the source.

I've now applied the idea from the last post, and changed the code from full inclusion to explicit instantiation. There are other changes to be made, but those will wait.

If you go back in the header's history, you'll see that the previous version used full inclusion. As such, this was included in every translation unit (TU) that used this code:

#include "boost/locale.hpp"
#include "windows.h"
#include <cassert>
#include <locale>
#include <sstream>
#include <string>

Not necessarily outrageous. However, all of this was included because of a couple of functions. Not only that, but because the implementation was in the header, some helper functions had to be declared in the header, too, thus leaking implementation details.

Now, applying our new-fangled design, here's what we get.

Header (win_console_out.h)

Contains just the declaration for the functions.

template <typename CharT>
std::string ConvertOutput(CharT const* s);

template <typename CharT>
std::string ConvertOutput(std::basic_string<CharT> const& s);

template <typename CharT = DefaultCharT, typename T = void>
std::string ConvertOutput(T const& t);

Because the only #include we need is <string>, everything else has been removed from the header.

Source (win_console_out.cpp)

Just as before, contains the non-template helper functions. These are not part of the interface, and were removed from the header.

Implementation (win_console_out.ipp)

Most of the previous content of the header ended up here - all the #includes, the declarations for the helper functions, and the template functions definitions.

Explicit instantiation

Finally, each user of this code will supply its own explicit instantiation. In this case, you can see here what we're defining:

template std::string ConvertOutput(char const* s);
template std::string ConvertOutput(

    std::basic_string<char> const& s);
template std::string ConvertOutput(wchar_t const* s);
template std::string ConvertOutput(

    std::basic_string<wchar_t> const& s);

And that's it. I'll use my extensive (hah!) body of published code as a test for this design. If it works, I plan to keep using it, in order to reveal its ugly warts.

I've also created the mechanism for reverting to full inclusion via #defined variables, either globally or on a header-by-header basis. As I usually say, I believe users should have the right to choose, and I strive to keep to that principle.

No comments:

Post a Comment