Sunday, August 30, 2026

Watch out for Visual Studio 2022's module support

C++ modules

 C++ modules were introduced in C++20 as an alternative to header files. Header files exist since its C legacy of the 70's so this is quite a chance. The biggest carrot dangled in front of you is the promised improved build times with slow build times as one the main pain points in C++. This marketing advertising is debatable: I made a test project with modules vs header files and the header files project wasn't slower. It needs to use pre-compiled header which is a non standard feature but every serious compiler implements it.

 There is fundamental problem with modules and that class definitions are not self contained anymore. I do not know all the module rules but it seems that classical headers defined in ixx (module interface unit) files are not included in a cpp file when the module is imported. The lack of pch in modules not only increases compilation time for the normal header includes but pch's often carried global macro definitions like the used Windows SDK version and the dreaded NOMINMAX define.

 Still the bait was just too big to pass by so I tried it out for a small (i.e. about 50 cpp files) project which builds fine with headers. That task took multiple days and became an exercise in frustration. I have never seen so many ICE's (Internal Compiler Error) since the old days of Visual Studio 4.2.

 The solution with classical headers uses a source and header file per class. We have conventions like to put forward declarations in a fwd file and global typedef's and constants in a types file. I mimicked this behavior by using a module partition ixx file for the class and a cpp for the implementation. The problems encountered in VS2022:

  • modules messes up when you use a forward file. A class definition may end up in a class declaration under certain conditions resulting in a spam of compilation errors. One cause is if the fwd file was imported after a partition import. Workaround: don't use the fwd file or import the fwd file before any other module partition.
  • modules have problems with DLL exported classes from classical headers. Workaroud: use e.g. 'export using ::Person;' in an ixx file. This did not always worked: sometimes it messed up the build.
  • modules gave ICE when exported data is visible through headers of the global module fragment of an ixx file. No workaround is possible only to circumvent this situation.
  • pointer to member function (in a header) complete confused the compiler resulting in failure to detect the type and pointer to member function. See below

export template <typename T, typename Id, Id (T::*Pmf)() const>
class TestContainer
{
public:
   void f()
   {
      std::for_each(m_vec.begin(), m_vec.end(),
                    [] (const auto& rptr) { (rptr.get()->*Pmf)(); });
   }

   std::vector<std::shared_ptr<T>>    m_vec;
}; 

The last one broke any progress but I found a workaround in the meantime. I assume this is a bug of VS2022 modules since the code is accepted by VS2022 in a header solution and also by Clang in compiler explorer.

Conclusion 

 Module support for VS2022 is just too buggy. Perhaps it works when all is modules but a mix of DLL libraries with classical headers and modules just doesn't work. I haven't tried VS2026 yet which may have solved many of these issues.

 I appreciate that the C++ committee took the build times issue serious. However C and C++ live half a decade with headers. Headers allow parallelization of compilation of translation units. Instead of inventing a new revolutionary mechanism I wonder if the build times couldn't be tackled by changes in the header mechanism. For example remove some preprocess phases (which the C++ committee already did); make templates easier parser-able even if that would break code. Header files need to be re-parsed for every translation unit due to changing definitions and macro's but can't a smart build system detect the need for this? I suppose there is only a small minority of header files which use these macro and definition tricks to change header behavior. There is one major exception and that is debug and release builds but on Visual Studio these are distinct builds in distinct directories.

Wednesday, August 19, 2026

Watch out for the range library compilation times

Ranges

 The ranges library are a great addition to the C++ standard. However they can increase compilation times; especially when used in template headers which are included by many other translation units. In our code base it caused the following error: 'error C1128: number of sections exceeded object file format limit: compile with /bigobj'. 

 The specific use case was a template class which used the range library in two ways:

  • use of ranges functions; e.g. std::ranges::adjacent_find(...)
  • use of a view; e.g.  std::ranges::binary_search(std::views::keys(deq), h);

It turned out that especially the view was heavy for the compiler. Leaving out the range in the code improved compile times with 30% for a piece of code and 10% overall in a large application. The numbers can o.f.c. vary per use case.

Conclusion 

 If compile times are paramount and you have a header which is included a lot and uses the range library you could measure its impact on compilation times. if it contributes too much replace it with pre range constructs.

Monday, July 27, 2026

Installing through IAssemblyCache

Windows Side-by-Side

 When you want to share your DLL's across applications there are a couple of options:

  • put in a directory and add a path variable
  • dump in the Windows system directory
  • put in the Windows Side-by-Side (WinSxS)
  • since Windows 7: using application config files and probing. This is the same as the first option but without a fragile global PATH variable. 

 Under Windows XP the first two options were not recommended. The third option was then the way to go but the documentation is not updated. Therefore one may question if it is still recommended.

To put something in the WinSxS one need:

  • a manifest
  • a signed cat file which lists all the installed components
  • one or more DLL's to install 

The following steps were executed:

  1.  Create a certificate in PowerShell with e.g. 'New-SelfSignedCertificate -Type CodeSigningCert -Subject "CN=SxSTestCert" -CertStoreLocation "Cert:\CurrentUser\My"'. This creates a SxSTestCert in the personnel section of the certification store. One can check this with 'cermgr.msc'.
  2. extract the public key and store in a *.cer file.
  3. create a publicKeyToken with SDK tool pktextract.exe from the just created *.cer file. Use calculated value for in the manifest file. This is a crucial step: with an incorrect publicKeyToken one get all kinds of errors.
  4. create a manifest (see below). The entries in the manifest file described the side by side assembly
  5. create a cdf file listing all files in the manifest file
  6. create a cat file from the cdf; e.g. 'makecat.exe Test.cdf'
  7. sign the cat file with the certificate with signtool. For example use 'signtool.exe sign /s My /n "SxSTestCert" /fd SHA256 /t http://timestamp.digicert.com Test.MyCompany.MyAssembly.cat' 

 A manifest file may look like:

 <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
    <assemblyIdentity 
        type="win32"
        name="Test.MyCompany.MyAssembly"
        version="1.0.0.0"
        processorArchitecture="amd64"
    publicKeyToken="91cb7a3ae2229a15"/>

<!-- Add your DLL file here -->
  <file name="MyPayload.dll">
  </file>
</assembly>

The associated cdf file may look like this:

 [CatalogHeader]
Name=Test.MyCompany.MyAssembly.cat
ResultDir=.
PublicVersion=1
CatalogVersion=2
HashAlgorithms=SHA256

[CatalogFiles]
<HASH>Test.MyCompany.MyAssembly.manifest=Test.MyCompany.MyAssembly.manifest
<HASH>MyPayload.dll=MyPayload.dll

Now that you have all files in code you have to invoke 'InstallAssembly'. Note that there is no sxs.lib to link against so the function 'CreateAssemblyCache' must be extracted from 'sxs.dll' through LoadLibrary / GetProcAddress.

#include <winsxs.h>
#include <atlbase.h>

void AssemblyCreate()
{
   CComPtr<IAssemblyCache> ptrCache; 
   HMDULE hModule = ::LoadLibrary(sxs.dll);

   using PfCreateAssemblyCache = HRESULT (*) (IAssemblyCache**, DWORD); 

   PfCreateAssemblyCache pfCreateAssemblyCache = reinterpret_cast<PfCreateAssemblyCache>(::GetProcAddress(hModule, "CreateAssemblyCache"));

   HRESULT hr = pfCreateAssemblyCache(&ptrCache, 0);
      
   FUSION_INSTALL_REFERENCE ref = { 0 };
   ref.cbSize              = sizeof(FUSION_INSTALL_REFERENCE);
   ref.dwFlags             = 0;
   ref.guidScheme          = FUSION_REFCOUNT_OPAQUE_STRING_GUID; // Required!
   ref.szIdentifier        = L"MyTestInstallerApp";
   ref.szNonCannonicalData = L"Test Installation";

   constexpr wchar_t szManifestFilePath[]  = L"Test.MyCompany.MyAssembly.manifest";
   
   hr = ptrCache->InstallAssembly(0, szManifestFilePath, &ref);

   if (SUCCEEDED(hr))
   {
   }
   ::FreeLibrary(hModule);

 This creates a side by side package:

  • on Windows 10 on C:\Windows\WinSxS and Manifests folders
  • on Windows 11 on C:\Windows\WinSxS\Fusion folder

Acknowledgement

 AI tooling Gemini guided me through the process of uncovering most of the information presented here. A couple of times I was steered in the wrong direction as well. For example on Windows 11 side-by-side components are installed in the Fusion subdirectory which was thought to be a .NET only location by Gemini. It suggested an alternative route through IAssemblyCacheItem but this yielded the same outcome. Only later I had to correct Gemini that Fusion is probably a Windows 11 thing. Claude first classified it as an outdated technology; later recommended this option based on the requirements until it concluded that installing in SxS would not be possible (based on SxS tracing) and only reserved for Microsoft themselves.

Sunday, April 26, 2026

Watch out for open plan offices

 

Open plan office

 I had to work for more than a decade in an open plan office which was far from ideal. Open plan offices in the Netherlands are actual open; not with cubicles like in the USA which would give some kind of privacy. There is a lot of visual and auditory noise in the open plan office which makes concentrating and doing your work difficult. Also when you need a meeting to discuss topics you have to reserve a room instead of just talking in your own room.

 The bad part was that I anticipated all this and reported this to my manager when he came up with the initiative to create an open plan office in the new building. He shove the arguments aside and said that I should first try it out before having an opinion. I countered that the old situation was already like an open plan office so I don't need new experience. He also used the dumb argument that he himself worked happily on an open plan office so I should be happy too. He then stated that if it wouldn't work out he would reverse the situation which of course never happened.

 He then put all developers in a big open plan office but he reserved for himself a nice private room. 

 I complained about the open plan office every assessment but nothing changed. When the open plan office was finally under scrutiny the manager even lied that the whole open plan office idea was on our request and now we suddenly wanted something differently. 

 He eventually got fired; not for his open plan office but actually for the wrong reasons. Still the performance drop due the open plan office would have been a strong argument to let him go. A new manager came and put me in a private room. Other developers weren't so lucky: they still had to endure the open plan office.

Thursday, January 1, 2026

Careful with refactoring

Refactoring issue

 Last year we applied a small refactoring in a piece of code. The construct was a parent - child relationship with the child hold by unique_ptr. Simplified the code was somewhat as follows:

struct Parent
{
   explicit Parent(bool bShow)
   : m_bShow(bShow)
   {
      m_ptr = std::make_unique<Child>(this);
   }

   std::unique_ptr<Child> m_ptr;
   bool                   m_bShow; 
};

struct Child
{
   explicit Child(Parent* pParent)
   : m_bShow(pParent->m_bShow)
   {
   }

   bool  m_bShow; 
};

 Although the code has a bidirectional dependency between parent and child iIt will compile if one splits out in header and source files for parent and child. 

 The heap use of child seemed redundant so it was removed and the child will be hold by value:

struct Parent
{
   explicit Parent(bool bShow)
   : m_child(this)
   , m_bShow(bShow)
   {
   }

   Child   m_child;
   bool    m_bShow; 
};

 This refactoring lead though to a bug where sometimes things were shown and sometimes not. The bug only appeared in release mode under certain conditions. It turned out that the value of 'm_bShow' was using uninitialized memory. We accidentally created a memory safety issue in years. 

 The problem is that the child is created before the parent is fully created and thereby using uninitialized memory of the parent. The solution is easy by reversing creation order:

struct Parent
{
   explicit Parent(bool bShow)
   : m_bShow(bShow)
   , m_child(this)
   {
   }

   bool    m_bShow; 
   Child   m_child;
};

 Using the 'this' pointer in constructor is a code alarm but not a code smell. As a general rule here declare first all members of built in types before declaring members with classes. If that is not viable one can defer creation by using std::optional instead of std::unique_ptr. std::optional is often more optimal from a performance perspective.

 Not sure how Rust would have prevented this; probably by disallowing the construct in the first place. That would be pity since circumventing the extra heap allocation and pointer access is certainly worthwhile the final solution where children are hold by value.

Thursday, December 25, 2025

Watch out for std::vector::at()

 Aspects of operator[] vs at() 

 In order to bump the default memory safety of C++ the committee has decided to harden the STL with adding bounds checking to operator[]. This is redundant since bounds checking is already present through function 'at()'. A safety profile could enforce use of 'at()' and issue a warning for the use of operator[].

 This hardening decision is not a free lunch and has consequences for performance. If we compare current operator[] which has no bounds checking with 'at()' with bounds checking it is 5 times slower in a test case. Below is the test case with two functions:

int g_iTemp = 0;

void PrfStlVectorIteratorIndex(const std::vector<int>& rv)
{
   int nTemp = 0;
   
   const size_t nLoop = rv.size();
   	
   for (size_t n = 0; n != nLoop; ++n)
   {
      nTemp += rv[n];
   }
   
   g_iTemp = nTemp;
}
   
void PrfStlVectorIteratorIndex(const std::vector<int>& rv)
{
   int nTemp = 0;
    
   const size_t nLoop = rv.size();
    
   for (size_t n = 0; n != nLoop; ++n1)
   {
      nTemp += rv.at(n);
   }
    
   g_iTemp = nTemp;
}

The results for a certain test with VS2022 17.14.23 with /O2: 

Function                  #            Total(s) 
PrfStlVectorIteratorIndex 1 0.149972
PrfStlVectorIteratorIndexAt 1 0.727781

The function using 'at()' is 5 times slower. A reason could be found when looking at the generated assembly. MSVC uses SIMD instructions in case of operator[] but it cannot use them with 'at()'.

Conclusion 

 This is a significant difference. It makes one wonder why the C++ committee took the decision so lightly to tax every invocation of operator[]. Especially since a major use case for operator[] is to use it in a loop as above where there is no danger of going out of bounds. The committee's argument is that it costed only 0.3% extra performance which clearly contradicts above numbers. Also they stated that on certain code bases it revealed thousand extra bugs. Not sure what that code base is. For decades we use Visual Studio with Microsoft's STL which has the extra checking turned on in debug mode and it never fires these asserts when using or testing debug builds. If it would fire it would reveal a bug and one can repair it. Let users who value safety over performance use the 'at()' variants but leave the operator[] alone.

 

Tuesday, December 23, 2025

Thoughts on C++ 26

Sutter's video

 The other day I watched Sutter's YouTube video about 3 cool things in C++ 26:

  1. Make C++ safer by replacing undefined behavior (UB) with erroneous behavior (EB)
  2. Reflection
  3. Yet another syntax for async

Safe C++ 

Sutter mentions two aspects:

  • uninitialized local variables will be data mangled. The compiler may inject code to check if uninitialized variables are accessed.
  • hardening of STL; most notably operator[] 
According to studies the overhead is minimal (0.3%). This number is debatable: they can never know what applications are out there. In the past we had bad experience with VS 2008 who turned on safe iterators in release builds. They killed all compiler optimizations right away when used.

I question also the first bullet: why not make it simpler and state that every variable will be default or zero initialized. There is no EB or UB necessary; or no hidden code injected by the compiler.

Some of the hardened STL functions are unnecessary. There are already 'at()' functions which bounds check. A safety profile could warn for use of operator[].

Reflection

Nice that reflection is added but I wonder if the C++ committee has the right priorities. The standard library even lacks a standard JSON or XML library which would be an ideal candidate for automatic serialization through reflection.

Async

They added a new superfluous new syntax. So much for consistency.

 Conclusion

 Memory safety is an issue these days even if so to silence the Rust religion. The C++ committee should do something though I believe more in safety profiles than changing the language fundamentally. Even so I would go then for zero initialization instead of checks with hidden costs. Reflection is nice but what C++ lacks most is standard libraries; not major language changes. Still for us as desktop application developer memory safety is not much of an issue.

Watch out for Visual Studio 2022's module support

C++ modules  C++ modules were introduced in C++20 as an alternative to header files. Header files exist since its C legacy of the 70's s...