150. Concepts, UB, interview questions, Boost

Media

Video

Podcast

Powered by RedCircle

Podcast

Starting with this meeting I’m synchronising podcast episode numbers with meeting numbers, because maintaining two numbering schemes is tiresome. Welcome to episode 150!

CppCast is on indefinite hiatus

Sad day for C++ podcast listeners. Rob Irwing, the creator of CppCast podcast (the 1st podcast for C++ developers) decided to hit pause for an indefinite amount of time. His new job is .NET which means he is not using C++, and he can’t justify spending time on it anymore, which is completely understandable. Redditors are grateful.

Great podcast. I’m a bit sad they’re invoking the CppCast destructor. I’ll always remember this as my favorite programming podcast!

CppCast had a good run, with Rob and his co-host Jason Turner producing an impressive total of 349 episodes over the years, all high quality content with many interesting guests. I mentioned CppCast a few times in our meetings, and I still have a long backlog to listen to, so it’s likely that I will continue mentioning it for some time.

All the best to Rob and Jason in anything that comes next.

Godbolt Compiler Explorer is 10 years old

Ten years ago Matt Godbolt open-sourced a web tool he built, Compiler Explorer. It turned into an indispensable tool for visualizing the work of compilers and code sharing. Most people probably just call it Godbolt now. And not only can you use it on its original website, but also self-host it too, in case you have additional security requirements or cannot share proprietary code online.

From the article:

Ten years ago I got permission to open source a little tool called GCC Explorer. I’d developed it over a week or so of spare time at my then-employer DRW in node.js, and the rest, as they say, is history.

This is what it looked like back then:

Matt mentions people who supported him and developer Compiler Explorer together, and shared some ideas for the future, like supporting more CPUs and architectures, user accounts, better support for GPUs, and actual support for mobile devices.

The Reddit thread is here.

QuickBench

QuickBench is an online microbenchmarking tool that allows you to compare performance of several code snippets using Google Benchmark. It draws nice charts and can also pass your code onto one of the publicly available C++ compilers, including Godbolt Compiler Explorer and C++ Insights.

C++ Insights

C++ Insights by Andreas Fertig is an online C++ tool that shows you an intermediate result of compilation of a snippet of code, so that you can see what actually happens in the compiler. The result is still C++ code but it is transformed to illustrate how the compiler sees your code.

std::hive update

The std::hive proposal P0447 was discussed in a committee telecon, and you will not believe what the next steps are:

Come back with revised paper addressing feedback.

How many more revisions will it take, I wonder?

C++20 Concepts and compile times

A redditor asks:

Do C++20 concepts change compile-time, positively or not? On one hand, a C++20 concept adds more stuff to digest for the compiler. On another hand, it imposes constraints, hence reducing SFINAE stages. Do you have some feedback about this?

One of the replies says:

Very positively. They nearly eliminate the need for SFINAE. And the code is much clearer - instead of weird template parameters, you have clean requirements listed. =>

Jonathan Wakely (GCC, RedHat) says that GCC has made a significant progress in Concepts compilation efficiency lately. He also notes that Concepts can be used for member function selection from the overload set and are better at it than SFINAE. Quote:

Concepts allow the constraints to be expressed in a simple form that takes less work to compile, and that can be optimized internally by the compiler more easily than SFINAE hacks.

Additionally, subsumption and partial ordering of constraints means that you don’t need to repeat the constraints on every overload, for example if the enable_if constraint above was used on one member of an overload set, you’d also need to negate that constraint on the other members <…>

Tristan Brindle raises an interesting point:

Just to add to this, another benefit of concepts is that they can be used in places where SFINAE can’t, like on destructors and copy/move constructors. This is useful for things like std::optional<T>, whose special members should be trivial whenever the equivalent operation on T is trivial.

His example code snippet is a partial declaration of optional class template that has two destructors: one defaulted for when the contained type is trivially destructible, with a requires clause after the parentheses that uses std::is_trivially_destructible, and another user-defined one for when the contained type is not trivially destructible, which has a body that calls the contained type destructor.

 1template <typename T>
 2class optional {
 3    // ...other stuff ...
 4
 5    ~optional() requires std::is_trivially_destructible_v<T> = default;
 6
 7    ~optional() {
 8        // ...manually call T destructor if engaged...
 9     }
10};

This leads nicely into the next topic.

Using Requires Expression in C++20 as a Standalone Feature

Rainer Grimm posted an article on his website about using requires outside Concepts.

You can use requires in static_assert:

1struct Foo
2{
3    int count() const { return 1; }
4};
5
6static_assert(requires(Foo foo) { { foo.count() } -> std::convertible_to<int>; });
7// ^ PASS
8static_assert(requires(Foo foo) { { foo.size() } -> std::convertible_to<int>; });
9// ^ FAIL

You can also use requires in constexpr if. Rainer Grimm presents the below function as a convenient alternative to SFINAE for getting a number of elements from an object no matter the name of the member function returning it.

 1template <typename T>
 2int getNumberOfElements(T t)
 3{
 4    if constexpr (requires(T t) { { t.count() } -> std::convertible_to<int>; })
 5    {
 6        return t.count();
 7    }
 8    if constexpr (requires(T t) { { t.size() } -> std::convertible_to<int>; })
 9    {
10        return t.size();
11    }
12    return 0;
13}

Another interesting use case is to put a constraint on a non-type template parameter:

1template <unsigned int i>
2requires (i <= 20)
3int sum(int j)
4{
5    return i + j;
6}

Rainer also describes anonymous concepts, or requires requires which attracted attention and undeserved ridicule of Concepts sceptics back when they were introduced. He says:

You can define an anonymous concept and directly use it. In general, you should not do it. Anonymous concepts make your code hard to read, and you cannot reuse your concepts.

Here is his example:

1template<typename T>
2requires requires (T x) { x + x; }
3auto add(T a, T b)
4{
5    return a + b;
6}

The first requires is the requires clause, and the second defines a requires expression.

I hope to get more into Concepts as I gain experience using C++20.

UB examples

Mohit Saini wrote an article called Shocking Examples of Undefined Behaviour. They are not that shocking if you already know about UB, but if it’s your first encounter, boy are you in for a surprise.

The first example of UB by Eric Musser is signed integer overflow combined with optimizer (Godbolt)

1int main() {
2  char buf[50] = "y";
3  for (int j = 0; j < 9; ++j) {
4    std::cout << (j * 0x20000001) << std::endl;
5    if (buf[0] == 'x') break;
6  }
7}

The first optimization step is a bit unintuitive but if you stare at it long enough it starts to make sense:

1for (int p = 0; p < 9 * 0x20000001; p += 0x20000001) {
2  std::cout << p << std::endl;
3  if (buf[0] == 'x') break;
4}

Here the compiler assumes that signed integer overflow never happens in a valid program. I think it ‘hoists’ the calculation one level up to the loop condition and increment.

The second step just turns p < 9 * 0x20000001 into true because the right-hand side of the comparison is larger than INT_MAX which cannot happen in a well-formed program. And so the loop becomes infinite!

I’ve seen the second example before (it was originally shared by Krister Walfridsson). This one is about dereferencing a null pointer. Let’s look at it in detail (Godbolt).

 1typedef int (*Function)();
 2
 3static Function Do;
 4
 5static int EraseAll() {
 6  std::cout << "Disaster Ahead" << std::endl;
 7  // system("rm -rf /");
 8  return 0;
 9}
10
11void NeverCalled() {
12  Do = EraseAll;
13}
14
15int main() {
16  return Do();
17}

The function NeverCalled() is never called explicitly. Nevertheless the function EraseAll() gets called, which is surprising and, well, potentially disastrous. How does that happen? This is what the compiler sees:

  • Function Do is static => it can only be modified by this translation unit.
  • The only possible values for Do are nullptr and EraseAll.
  • Since Do is getting called, it cannot be nullptr as it would be UB and we can’t have that in a well-formed program.
  • This means that Do can be initialized with EraseAll and NeverCalled can be replaced by no-op.
  • And so it happens that main now just calls EraseAll. Boom!

In the Reddit thread people agree that UB is fascinating but not in a good way.

A redditor suggests using the flag -fwrapv for making signed integer overflow defined use 2-complement arithmetic.

Another redditor attempted to suggest using unsigned integers with defined overflow semantics, to which there was a reply:

The problem is, they have very unhelpful behaviour a lot of the time, and are widely considered a mistake in e.g. the standard library

Glad we got that solved and will never need to revisit it again ever.

As ever there is someone telling us that Rust does it better.

I like this quote:

The UB comes first. The unexpected results follow.

Nameof

A Ukrainian developer living in Kyiv, Daniil Goncharov, developed a utility library nameof for obtaining a string representation of pretty much any C++ object at compile time, be it a variable, a member variable, a type, or even a macro. Lots of magic in a single header file, which can be a great help for logging and debugging.

The library comes under MIT licence, supports Windows, Linux and macOS, and can be installed using vcpkg, or just by incorporating the header into your project.

Much of the functionality uses a compiler-specific hack based on __PRETTY_FUNCTION__ and __FUNCSIG__, which works on Clang >= 5, MSVC >= 15.3 and GCC >= 9.

See also the discussion on Reddit.

While browsing Daniil’s code, consider supporting Ukraine 🇺🇦 in the fight against Russian invaders.

Xmake 2.6.6

Xmake 2.6.6 has been released. Xmake is a very capable cross-platform build utility and package manager based on Lua. It supports other package managers like Conan and vcpkg out of the box. It also supports modern C++ features like modules, generates project files for Visual Studio, CMake, and Xcode, comes with sensible defaults and is generally very pleasant to use.

New in this version:

  • Support for NVIDIA HPC SDK C, C++, and Fortran
  • Support for distributed compilation which:
    • is cross-platform
    • works with msvc, clang, gcc
    • builds android, ios, linux, win, macOS programs
    • has no dependencies other than the compilation toolchain
    • supports load balancing scheduling
    • supports real-time large file compression transfer (use lz4)
    • has almost zero configuration cost (no shared file system required)

Give Xmake a go for your next pet project.

C++ interview questions

A redditor asks:

What are some C++ related questions that you have been asked in a job interview?

A sampling of replies from the thread:

  • What is polymorphism? Make a polymorphic container.
  • What are smart pointers? Prove you can use them.
  • Explain virtual tables.
  • how do you allocate memory in C++? How does it differ from C?
  • “In Java they’ve got try, catch, and finally - the latter performs some code no matter whether an exception was thrown or not. We’ve got no ‘finally’ in C++, how do we achieve the same effect nonetheless?” => – RAII is the best answer here.
  • “I had my last C++ interview 3 years ago and references weren’t mentioned at all. Then again, I’m in the game industry which is notoriously resistant to modern C++.” =>
  • “You need to push 1 million elements to the container using push_back method, which container will you choose, std::vector or std::list and why? And the follow up question was how many memory allocations will happen, if you use push_back in vector to push 1 million elements?” => – The no. of allocations depends on implementation but will often be log2(1'000'000) because by default vector allocates 2x capacity each time it runs out of space. A better solution is to pre-allocate the vector, of course.
  • What is the difference between make_shared and shared pointer constructor?
  • What is the difference between a class and a struct? (the default access level)
  • What is the difference between a struct and a union?
  • What is virtual inheritance?
  • What is a union used for?
  • Rule of five, rule of 3, RAII, what do they mean?
  • What’s the difference between a reference and a pointer?
  • What does the keyword volatile mean?
  • What are the different types of cast operations?
  • What does the inline keyword do?
  • Explain the slicing problem.
  • Explain how SFINAE works in pre-C++20.
  • Explain what a cross-thread data race is.
  • If a class has a destructor, it should probably also have a [blank] and a [blank]. (And maybe another 2 x [blank]).

A couple of code snippets:

What is wrong with the following class declaration?

 1class String
 2{
 3public:
 4   String(const char* s) : len(strlen(s)+1), str(new char[len])
 5   {
 6      std::copy(s, s + len, str);
 7   }
 8
 9   // More member functions declared here
10
11private:
12   char* str;
13   size_t len;
14};

Answer: str will be initialized before len as member variables are initialized in declaration order, and since len is uninitialized, it contains garbage, and accessing it is UB.

What will be printed?

 1class A
 2{
 3public:
 4    A() { foo(); }
 5    virtual void foo() { std::cout << "A::foo\n"; }
 6};
 7
 8class B : public A
 9{
10public:
11    virtual void foo() { std::cout << "B::foo\n"; }
12};
13
14int main()
15{
16    B b;
17}

Answer: A::foo, as calling virtual functions from a constructor makes them behave like non-virtual functions (and is generally not a good idea).

I remember reading a C++ hiring advice some time ago: ask the candidate a complex template metaprogramming question, and if they answer correctly, don’t hire them.

Is Boost still relevant?

A redditor asks:

Is learning Boost still essential with C++20?

This looks like a sensible answer to me:

I am not sure “learning” Boost is the right term, maybe understanding that it exists and take advantage of it if necessary.

What I tell my new developers is that if you need a container or algorithm or something that is generic and not specific to our business, is to first check the STL and core C++ libraries, then Boost and if it is not there, look for another open source option. If you can’t find something that fits what we need, code it yourself or modify one of the existing implementations if that would make more sense.

Boost incubates a lot of code before it goes into the C++ standard so often has things you can’t find in C++20. =>

Some of the Boost libraries that could be still useful with C++20:

Another great answer from the thread:

There’s no single thing from boost that I have a need for in every project, but I nearly always use something. Here’s everything I’ve used this year, from memory:

None of these are essential but just having boost as a dependency changes certain problems from either ‘find and install an entirely new library’ or ‘implement a whole mini-library yourself’ to ‘include one header’.

Pure Virtual C++ 2022 Recordings Available

Sy Brand writes:

Pure Virtual C++, a free one-day virtual conference for the whole C++ community, ran on 26th April 2022. All sessions are now available to watch online.

The titles sound quite interesting:

I need to watch these videos at some point.

libassert

Jeremy Rifkin wrote a super-advanced assertion library, libassert. In his own words, libassert is

The most over-engineered and overpowered C++ assertion library.

When an assertion fails, not only do you get a message, a stack trace, and local variable values, but these all are syntax-highlighted!

This failed assertion:

1assert(vec.size() > min_items(), "vector doesn't have enough items", vec);

results in the following terminal output:

Supported constructs include:

  • DEBUG_ASSERT - similar to assert()
  • ASSERT - checked at run time
  • ASSUME - for checking assumptions and preconditions in both debug and release
  • VERIFY - for checking conditions in both debug and release

The library requires C++17 and is distributed under MIT licence. There is also a Reddit thread.

Quotes

Fred Brooks:

What one programmer can do in one month, two programmers can do in two months.

Twitter: JavaScript comparison