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 promote 'at()' and issue a warning for the use of operator[].

 This decision has also consequences for performance. If we compare current operator[] which has no bounds checking with 'at()' with bounds checking it is 5 times slower. Consider the following 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. Spying the assembly it seems that 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. Their 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 testing debug builds (which is what programmers do all the time). If it would fire you found a bug and 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 but I believe more in safety profiles than changing the language. 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.

Saturday, November 8, 2025

Issues with Linux port

 

Linux port

 The company who employs me has decided to port parts of our application to Linux. At a first shot we will use WSL and Visual Studio but issues are not over:

  • CMake is the lingua franca of generating cross platform build scripts. CMake is a beast of itself however and not sure why this got so popular.
  • WSL keeps sometimes its old configuration and source files. It seems that when the source files are read only they are read only on the target WSL system as well. If you edit a file afterwards the update of this file will fail so one won't see their changes. Either make the source files not read only beforehand or clean all files and directories on the WSL host and then start fresh again. Alternatively make the source files writable on Linux through chmod. 
  • MSVC uses __declspec(dllexport) to export functions from DLL's; GCC doesn't have that.
  • MSVC is pushing security enhanced versions of the CRT through its code analyzer. According to cppreference these functions are standardized albeit as extension (i.e. Annex K of C11). Unfortunately glibc has not implemented them; partially because some dubious reasoning. The API isn't perfect and there are pre-exisiting bounds checking crt but still it's standardized and some people (we) use them. So one ends up writing Windows and Linux specific code even in a layer which supposed to be platform independent.
  • Many of the MSVC C API is Windows specific (e.g. _splitpath; _makepath; _tchdir). Using C alone on Windows platform may therefore still not platform independent.
  • GGC bugs:
    • Warning suppression's in pre-compiled header in GCC are ignored in code. This is quite unhandy; especially since some suppression's one want to apply globally to all sources and are therefore are primary candidate to put in pre-compiled header. I have filed a bug 123287 report and it's stated that this has been solved for GCC 15.x.
    • std::chrono::parse does not support wchar_t.
    • erroneous message about possibly dangling reference to a temporary. Fixed in GCC 14.3
    • GCC can't handle init of boost::uuids::uuid with a 16 element std::uint8_t array. Fixed in GCC 15.2.
  • On Windows wchar_t are 2 bytes and represent UCS-2 or UTF-16. On Linux wchar_t it is 4 bytes and probably represents UTF-32. To be compatible with existing persistence storage we had to use char16_t on certain places in the code. Unfortunately it seems that some character code conversion facilities are deprecated so this solution will not hold out for long.
  • std::basic_ifstream and std::basic_ofstream don't accept std::wstring as function name on Linux. This seems to be a MSVC extension so change code to use std::filesystem::path which is a conformance improvement.
  • __FUNCTION__ is an extension which both MSVC and GCC understand. On GCC it is not a macro so prepending it with 'L' to get the wide character variant does not work. It also only gives the function name without class in case member function which makes in unattractive. So specific MSVC and GCC code is needed. There is a standard: __func__. However again it only gives the function name. source_location is another alternative but this gives too much information for the function name. 
  • std::prev and std::advance with negative offset on GCC increment the iterator in case it's an input- or forward iterator. This lead to a crash with 'std::prev(umap.cend())' since on GCC std::unordered_map iterators are of forward iterator category type while on MSVC they are bidirectional. Also using std::views::transform can result in an input iterator category which would fail then with std::prev. See bug 122224. Preferably a compile time error should be issued but GCC author thinks otherwise. Decided not to use std::prev.
  • no leading zero mask in date time (e.g. the'#' in '%#d') is a MSVC extension. Resolution: write ourselves.
  • swprintf requires '%ls' for wchar_t arrays. This is unfortunate for writing char / wchar_t agnostic code.

GCC might still contain some basic bugs. The warning about #pragma once in main file (when building a pre-compiled header) is only solved in version 14.

Saturday, October 18, 2025

Using clang-cl in Visual Studio

clang-cl

 clang-cl is the command line tool in Visual Studio capable of invoking the clang compiler with the arguments of msvc. In Visual Studio projects one can just flip the toolset and the clang compiler will be chosen. clang has the following positive aspects:

  • better C++ conformance. Examples are that msvc is leniant towards missing 'typename' for dependent types and 'template' for nesting templates; clang picks them up. There are other issues.
  • offers some code improvements like correct member order in constructors and virtuals which override base class
  • detects some performance improvements like advising to use shared_ptr by reference in loops
  • more precise compilation warnings and errors

It has also some drawbacks: 

  • some noisy warnings 
  • does not understand all msvc code. For example the msvc's #import extension is not understood

 Despite using msvc's code analysis the clang compiler was still able to pick up other issues. Some clang warnings are far fetched and one can choose to disable them. This is especially needed for external libraries which one cannot easily patch. To disable warnings one can use the following:

#ifdef __clang__
#pragma clang diagnostic ignored "-Wimplicit-exception-spec-mismatch"
#pragma clang diagnostic ignored "-Wmissing-field-initializers"
#pragma clang diagnostic ignored "-W#pragma-messages"
#pragma clang diagnostic ignored "-Wunused-but-set-variable"
#pragma clang diagnostic ignored "-Wunused-local-typedef"
#endif

 The first one for example is necessary to suppress warnings in MFC. 'delete' should be specified with 'noexcept' but the MFC delete lacks this.


Monday, August 25, 2025

Watch out for hypes in ICT

Hypes

 ICT has a rich history of hypes where people thought that this would be a panacea for all problems. These hypes lasted for some time like paradigms in Thomas Kuhn's theory about evolution of science. From the top of my head we had the following hypes in the past:

  • relational / SQL databases (70's)
  • structural design
  • object oriented design (80's)
  • component based development (90's)
  • design patterns (1995)
  • scrum / agile (2001)
  • AI (2022)

 Many of these hypes were initially promising but not to the extend of solving all problems. They are now part of the current solution domain. We also know now that there are still problems to tackle. 

 Let's see what AI will bring us in the future. For now it's on the level of coding assist but not on the level of designing whole systems. In that part it still cannot replace programmers. There are already studies mitigating the effect of using AI.  It also still makes mistakes. From personal experience it can introduce errors in a code base if you let it run without crosschecking.

 Scrum has brought nothing to ICT except misery. The company I work for took a major loss after embracing it. 

Saturday, August 16, 2025

Careful with AI tooling

AI tooling

 Since some period I started working with AI tooling. Mostly I use Gemini and Copilot inside Visual Studio. The experience is a bit of mixed feelings about this. Gemini had some good suggestions but failed also many times. Copilot has good code completion suggestions but misses the mark also. Copilot's function name suggestion are very welcome.

 On the other hand AI tooling is still full of mistakes. Some examples:

  • I asked Gemini for camera sharpness algorithm. It came up with a good algorithm but the actual OpenCV function calls and parameters were incorrect.
  • I asked Gemini to get the real sample time from an 'IMediaSample'. It suggest to use the non existing 'GetSampleTime'. There is b.t.w. a 'GetMediaTime' function but this returns the stream time; i.e. the time since the graph was running and not the time from the start of the video. 
  • I asked Gemini lately of conversion from UCS-2 to UTF-16 and it wrongly suggested to use wstring_convert. However wstring_covert is hardbound to std::string as byte_string
  • I asked Gemini for a natural sort algorithm. It came up with a good implementation and a clever trick to circumvent conversion to integer numbers. It only lacked the removal of leading zero's which would have disrupted the clever trick.

 Even worse that sometimes AI tooling can suggest plain bugs. I was implementing a swap of width and height and Copilot's code complete came up with the following code snippet:

// NOTE: incorrect 
Size sz = ...;
if (sz.GetWidth() < sz.GetHeight())
{
   sz.SetWidth(sz.GetHeight());
   sz.SetHeight(sz.GetWidth());
}

This doesn't swap but sets the width and height on the old height value.

 AI tooling can be helpful but are still not on the level to be trusted blindly. They also now help with limited scope; e.g. code blocks; algorithms and functions. I am not aware if they can help in refactoring and extending architecture spanning solutions.

 

Debugging GDI drawing

GDI debugging

 The other day I had to debug a hard to track drawing bug. The application is built with the MFC framework so it still uses GDI on places to draw custom controls.

 The incorrect drawing artifact was displayed after an invocation of 'DrawText' with the flag 'DT_CALCRECT'. This was unexpected since with the flag the function doesn't draw and only measures the size. Eventually I realized that GDI batches invocations so perhaps the buggy overdrawing had already taken place before. What was needed to prove this hypothesis:

  •  suppress GDI's caching mechanism through 'GdiSetBatchLimit'.
  •  use direct drawing; so no memory device context

 With this in place indeed it could be seen that the mistake happened earlier in the code and that the 'DrawText' invocation was merely a flush of the GDI batch.

 Be aware that suppressing  GDI's batch might not always work. When the window where the drawing took place was on the primary monitor the batch mode could be turned off but on the second monitor it still cached its calls.

Sunday, July 27, 2025

Watch out for an old VC++ runtime

 VC_redist.x64.exe

 For C/ C++ applications the VC++ runtime needs to be installed on the computer. The other day we experienced crashes when a component developed with a late version of VS2022 was crashing on a fresh installation of Windows 11. It turned out that this Windows 11 still uses an old version of the VC++ runtime which could crash the application (most notably in grabbing a std::mutex lock). After updating the PC with a recent version of 'VC_redist.x64.exe' the problem was solved.

 

Wednesday, July 23, 2025

ark.intel.com

 

ark.intel.com

 Intel had a wonderful website where one could easily lookup the processor and see what capabilities (e.g. SSE 4.2; AVX; AVX2) it had. In a recent visit it was completely overhauled and they have removed (or hidden) the easy possibility to lookup your processor with one click. Thanks Intel for modernizing their website and destroying a valuable functionality.


Sunday, June 29, 2025

Watch out for atan change in Visual Studio 2022 17.14.6

atan

 Recently we updated Visual Studio 2022 17.14.6 and the regression test reported errors. It turned out that atan implementation was changed resulting a different value for debug vs release builts with CPU's having AVX2. One can recreate this with the following values:

    constexpr double ax        = 38.176459921094995;
    constexpr double ay        = 15.964755390006060;
    const double     dRotation = std::atan(ay/ax);

 We had to relax the equality checks; even for deterministic calculations.


Sunday, June 1, 2025

Careful with std::wfstream

wchar_t file streams

 The std::wfstream is similar to std::fstream except it accepts wchar_t. However it does not write std::wchar_t characters to file. Suppose the following code:

   std::wofstream ofs{L"c:\\temp\\1.txt" , std::ios_base::out | std::ios_base::binary};
   ofs.write(L"ABC", 3);

 On the Windows platform this writes just single bytes characters to the file. It uses the codecvt of the imbued locale which translated wchar_t to char. The standard C locale does not handle characters above the 255 so it will fail when using other characters than the extended ASCII character set. It will also fail when writing binary data through the write interface. It can be fixed by using a custom locale which leaves wchar_t unaffected. There was a codeproject article on this but it has been retracted.

 This translation is quite unexpected behavior since the function prototypes are defined in terms of wchar_t. It is also different compared to the wchar_t string streams: std::wstringstream does write wchar_t strings unaffected.

 This article was inspired by a YouTube comment of me where I stated that the C file stream API is less surprising. Of course there is always a clown who thinks better but probably doesn't know anything about above issue. With C stream I/O FILE and 'fwrite' the bytes are transferred to the file without interpretation and alteration.

FILE wrapper

 Jason Turner goes a lengthy way of wrapping the C file stream API but using a wrapper class would probably be simpler:

class c_file
{
public:
   c_file()
      : m_fp(nullptr)
   {
   }
   
   explicit c_file(const std::filesystem::path& rpth, const char* pszMode)
      : m_fp(fopen(rpth.string().c_str(), pszMode))
   {
   }

   ~c_file()
   {
      if (m_fp)
      {
         fclose(m_fp);
      }
   }

   c_file(const c_file&) = delete;

   c_file(c_file&& rOther) noexcept
      : m_fp(std::exchange(rOther.m_fp, nullptr))
   {
   }

   c_file& operator=(const c_file&) = delete;

   c_file& operator=(c_file&& rOther) noexcept
   {
      std::swap(m_fp, rOther.m_fp);
   }

   explicit operator bool() const
   {
      return m_fp;
   }

   size_t read(void* pBuffer, size_t size, size_t count)
   {
      return fread(pBuffer, size, count, m_fp);
   }

   size_t write(const void* pBuffer, size_t size, size_t count)
   {
      return fwrite(pBuffer, size, count, m_fp);
   }

   // etc.

private:
   FILE*    m_fp;
};

Links

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 chi...