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;
   ofs.open(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. The reason is that the locale bound to the file stream translates wchar_t to char. This can be fixed by using a custom locale which doesn't do this. 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 page was inspired by a YouTube comment of me where I stated that the C FILE 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 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

Sunday, July 21, 2024

C++ horrible aspects

C++ horrible aspects

 Linus Torvalds described C++ as being a horrible language. While C++ has its dark corners I choose it any day over any other language. Especially C with its major liabilities. There are some questionable aspects though: 

  • 'rvalue' references becoming 'lvalue' references. Not sure who invented this but he should be banned from the committee. Super confusing that a type can change.
  • universal references and perfect forwarding. Again a very confusing idea to reuse the 'rvalue' reference syntax. The reference collapsing rules doesn't make it easier either
  • two phase lookup. Confusing rule which could have been solved by stating that a template definition is only checked at instantiation time. No more need for 'typename' or making types and member functions (non) dependent
  • lack of uniformity in STL. For example there is reset and clear.
  • functional programming style over object oriented. Why not make the regex functions member functions? It prevents clutter of std namespace. It will probably also help intellisense to build up its database which is important help for programming these days. 
  • uniform initialization. Again the committee f*cked up. Could be fixed by using double braces (e.g.std::vector<size_t>{{2}})

Besides these aspects C++ misses out on an extended standard library. Especially compared to .NET or Java where many things are present in their extensive standard library. One need frequent 3th party libraries (e.g. Boost) for basic things. This could have been alleviated if there was a standard package manger like in Python with de facto libraries but again there isn't.


Monday, June 24, 2024

Careful with that initializer_list part 2

initializer_list

 When using Boost.JSON I stumbled upon the following issue:

boost::json::value jv(1);  // creates a number type
boost::json::value jv{1};  // creates an array type

 The value object is something like this:

struct Value
{
   Value(int);
   Value(double);
   Value(std::initializer_list<int>);
};

 This gives the following invocations:

Value vl1(1);  // invokes Value(int) constructor
Value vl2{1};  // invokes Value(std::initializer_list<int>); constructor

 This is a know issue in C++. A programming language should be unambiguously be interpretable and the C++ had decided that in such case the initializer_list has precedence. Not sure if that's a good solution since the ambiguity may only appear when running under the debugger or at customer site. Personally I would require that double braces are used but the C++ committee has decided otherwise. The uniform initialization problem is still not solved.

 

Wednesday, August 30, 2023

Careful with std::ranges

<ranges>

  C++20 has added the the ranges library. Basically it works on ranges instead of iterators but added some subtle constraints to some algorithms. For example consider the lower_bound algorithm:

#include <algorithm>
#include <utility>

using IntPair = std::pair<int, int>;
   
IntPair a[1];

auto it = std::lower_bound(std::cbegin(a), std::cend(a), 1, [](const IntPair& r, int n) { return r.first < n; });

The lower_bound function only expects an asymmetric functor implementing the order between container and search element. To spare on typing out the begin and end iterator one could think to use the ranges library:

auto it = std::ranges::lower_bound(a, 1, [](const IntPair& r, int n) { return r.first < n; });

This gives though a ton of mystic error messages on VStudio:

1>error C2672: 'operator __surrogate_func': no matching overloaded function found
1>error C7602: 'std::ranges::_Lower_bound_fn::operator ()': the associated constraints are not satisfied
1>message : see declaration of 'std::ranges::_Lower_bound_fn::operator ()'

It turns out that the ranges variant expect a functor with all less combinations defined:

struct OpLess
{
   bool operator() (const int n1, int n2) const                 { return n1 < n2; };
   bool operator() (const IntPair& r1, const IntPair& r2) const { return r1.first < r2.first; };
   bool operator() (const IntPair& r, int n) const              { return r.first < n; };
   bool operator() (int n, const IntPair& r) const              { return n < r.first; };
};

auto it = std::ranges::lower_bound(a, 1, OpLess{});

Side note: concepts supposed to give more clearer error messages but are cryptic as well.

External links

Sunday, May 7, 2023

Careful with std::shared_ptr

std::shared_ptr

std::shared_ptr is a C++ smart pointer who takes shared ownership of the pointee. It solves some of the memory problems associated with the C language which are memory leaks; buffer overruns and dangling pointers. The first two can be solved by using std::vector; the first and last one by using std::shared_ptr. shared_ptr has though some sharp edges:

  • one can create cycles between std::shared_ptr (i.e. A points to B; B points to A). The memory isn't released then. Solution is to use std::weak_ptr to break the cycle. Alternatively one can use a raw pointer to point back to the owner.
  • never assign a 'raw' resource to two std::shared_ptr's. Instead once a resource is assigned to a std::shared_ptr use the std::shared_ptr to share that resource.
  • use enabled_shared_from_this to hand out a std::shared_ptr of yourself. This fails in constructor because the std::shared_ptr structure is build after the constructor returns.

Garbage collectors don't suffer from these issues but the runtime price one pays for it is large. Also their non-deterministic destruction may another big hurdle to coop with.

Saturday, January 21, 2023

Scrum is the cancer of ICT

Scrum

  The company I work for decided 5 years ago to switch to a more Agile / Scrum development method. A consultant promised the CEO that with this method we would be more effective and he used some bullshit use case found on internet. 

Since then two major projects have failed due to the following reasons:

  • sprints of 3 weeks are too short to achieve substantial development
  • focus on short term - easy achievable goals. A pitfall fueled by easy to check off backlog items and tasks
  • work on core and foundation has not much attention. Hard to show and demonstrate to your stakeholders this kind of work.
  • make small pieces and of software and patch / refactor later is not a way to build stable buildings let alone software
  • lot of administrative overhead with daily standup's; sprint review; grooming sessions; retrospective etc.
  • Agile promised to offer better dealing with shifting requirements but this is a false promise.

Not sure why the world has gone mad but our company was doing a lot better beforehand. A false fallacy is often used to compare this method with the waterfall method with rigid requirements once written down. However no company works that way.


Tuesday, August 9, 2022

Watch out for MSVC's std::map's move constructor

move constructor

 The other day I found out that MSVC's std::map move constructor doesn't have noexcept:

map(map&& _Right) : _Mybase(_STD move(_Right)) {}

This means that it can throw when moved and it's not eligible for move_if_noexcept which is used in vector reallocation's. I filed a report but it got rejected. Take care when you want map's to be moveable (which can be a factor compared to copying all nodes).

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