How to become a conference speaker


A reader of my book C++ Brain Teasers recently emailed me and asked:

By the way, I’m curious how one gets to a point in their C++ journey / career when they’re able to give talks at these large conventions. Do you have any insights on this? 

I figured I’d reply in blog form, in case someone else is wondering the same. Note that while I’ve spoken at conferences like CppCon, Meeting C++, and others, I’m by no means a big fish speaker. But I at least have the advantage of explaining it while still remembering how it is to be new in the game.

So, how does one become a speaker at these conventions? Let’s first clarify a few important points:

1. Speakers are not special

Conference speakers might seem like they know more than the rest of us, and one might think only the best programmers become speakers. This is not true. Several of the best programmers I’ve worked with, who are certainly more knowledgable and smarter than me, have never spoken at a conference. To speak at a conference, you don’t need to be best, you just need to have something you want to tell, and be decent at presenting it. (See below if you’re not yet experienced with presenting.)

2. Speakers don’t know it all

When I’m in room A talking about CPU memory models, someone else is in room B talking about coroutines. It’s easy to think that all the speakers know all these things. Most of us don’t. I have no idea how coroutines work. Last week I had to google how to use std::cin.

What I’m saying is that you too can become a speaker, you don’t need to be the smartest or most experienced person in your team / class / group of friends. You just need to love learning, and be motivated to try speaking about what you’ve learned.

Getting started

So how do you get started, then? Speaking is a skill, just like programming. The more you do it, the better you’ll become at it. Here are some tips to get started:

  • If there are tech talks at work, volunteer to give one. If there aren’t, start organizing some. In my experience, management tends to be very happy when someone steps up to improve the skills of the team. Start simple, 10 minute lightning talks before lunch every second week or something. Give the first talk yourself, then ask others to present too. And if your first talk isn’t great, even better, then the bar is lower for the next person! :) The talks can be on something you feel others need to know, something you want to learn about, some language feature, some cool new thing someone did at work, etc.
  • If you’re still in uni/college, volunteer to help your fellow students. This will give you experience with teaching and explaining things. Try to get a job as a teaching assistant.
  • Seek out local user groups. Use a search engine, check meetup.com, etc. In Oslo we have Oslo C++ Users Group, there might be something similar near you. See if they have some lightning talk evenings coming up. If not, message the admin and ask. They tend to like it when people take initiative for something to happen.
  • Be curious! Read, watch other talks, try things out. If you’re not somewhat passionate about this, chances are you won’t enjoy speaking. It’s quite some work, and typically not paid.

But what should I speak about?

Try to focus on something specific for a while

My best trick for coming up with a talk is to work on something specific for a good amount of time. For instance, in once company I worked at, I took the main responsibility for linking and physical architecture. Whenever there was an issue related to linking, symbols, etc., I tried to get myself assigned to it. This taught me a lot, and resulted in three conference talks.

Write a talk about something you want to learn

To be honest, this approach is a lot of work, but it can be a great way to come up with a talk. For instance, at one point I wanted to better understand the Assembly output on https://godbolt.org. I had learned a tiny bit of Assembly in college a long time ago, but had forgotten most of it. I wasn’t able to find a good talk about it, so I figured, hey, let’s learn this, and give that talk that the world is missing. It took weeks, but resulted in my most successful talk Just Enough Assembly for Compiler Explorer, which I’ve presented at many conferences. While it’s a lot more work to write a talk about something you don’t already know well, you have one big advantage – you still know what’s hard about the topic. It’s also fun to learn, and in my experience, the best way to learn something is to attempt to explain it to others.

Talk about what you wish more people knew / did / cared about

This works especially well for lightning talks. Have you recently discovered a super useful tool that more people should know about? Are you driven nuts by people using technique X wrong? Give a lightning talk about it.

Preparing and giving the presentation

There’s a lot to say about preparing and giving a presentation; too much to cover here, and others have done it better. But make sure to prepare well in advance, use high contrast and a large font, and rehearse a lot. Even the most experienced speakers like Matt Godbolt rehearse their talks over and over.

Have fun speaking!

CppQuiz.org is now on C++23


If you’re not familiar with https://cppquiz.org, it is, as its name suggests, a C++ quiz site. Each quiz is a full C++ program, and your task is to figure out what the output is. But the real value often lies in the explanation, which goes into detail about why the answer is what it is. The explanations typically reference the standard quite a lot, so it’s a lot of work to port all of them whenever a new standard is published.

Thanks to some great help from the community, especially @tocic, we managed to port the whole site over the summer. So now you can enjoy up-to-date questions and explanations using the very latest C++23!

As promised in Help Get CppQuiz to C++23 And Win a Book, three contributors were drawn to win a copy of my book C++ Brain Teasers. Below is a recording of that:

Again, thanks a lot to everyone who contributed, and congratulations to the three winners! You will be contacted by email to arrange for shipping.

C++ Brain Teasers Book Launch September 10 (live+streaming)


On September 10 there will be a book launch event for my book C++ Brain Teasers organized by Oslo C++ Users Group at NDC TechTown in Kongsberg. The event starts with food and mingling at 18:00 CEST, and the book launch starts at 18:30. Check out the event at Meetup.com for more details. Update: Here’s the recording of the event.

The event will start with an interview by Olve Maudal, then we’ll do one chapter as a mini-quiz and I will read that chapter. There will be a few physical copies for sale and a discount on the e-book.

I will also attempt to stream the event on YouTube, so if you can’t make it to Kongsberg, head over to my YouTube channel at https://www.youtube.com/@andersknatten and watch it from wherever you are in the world!

I wrote a C++ book!


I’m very proud to announce that my first book just got released on The Pragmatic Programmers! The book is called “C++ Brain Teasers“, and is part of their Brain Teasers series.

The book consists of 25 short C++ programs, and the point is to guess what the output is, and why the language works like that. Much like CppQuiz.org, except with more elaborate and well-written explanations explaining the underlying principles of the language. The puzzles were also selected to be more cohesive and relevant to real-world uses, and the explanations include lots of practical tips to write better and safer code in practice. So the book can be read just for fun or as a deeper learning opportunity, and maybe you can keep a few copies for entertainment around the office?

From the marketing blurb:

C++ is famous for getting all the default behaviors wrong and for sometimes making demons fly out of your nose. Through 25 puzzles, from the useful to the outright weird, we explore some of C++ ‘s most interesting quirks. How does initialization actually work? Do temporaries even exist? Why is +!!”” a valid expression in C++ ? As you work through each puzzle, you will peel off some of the layers of complexity of C++ , getting a fundamental understanding of how the language works. This will help you write better code and recognize issues more easily while debugging.

The book is available both in paperback and as an e-book, and can be purchased from the publisher or wherever you normally buy books. You can even download the preface and three full chapters for free, with no registration needed! I find it especially cool to be published on The Pragmatic Programmers, as “The Pragmatic Programmer” was an important and transformative book early on in my career. I even got support from Dave Thomas himself at one point! 🤩

Finally, a big thank you to Frances Buontempo for introducing me to the publisher, and to my amazing technical reviewers Daniela Engert, Björn Fahller, Olve Maudal, Karthik Nishanth, Tom Schultz, Tina Ulbrich, Sergei Vasilchenko, and Piotr Wierciński!

Happy reading!

Help get CppQuiz to C++23 and win a book!


CppQuiz.org is currently using C++ 17 for explanations and needs porting to C++ 23. I’d really appreciate your help! As a thank you, three contributors will get a copy of my upcoming book C++ Brain Teasers.

How do I help?

All the questions from the site have been exported to https://github.com/knatten/cppquiz23. Full instructions are in the README, but in summary, you just have to pick a question and update the explanation to refer to the C++ 23 standard instead of the C++ 17 one. Usually, there are just a few references that need updating (for instance, something moved from §[basic.type.qualifier]¶6 to §[basic.type.qualifier]¶3), sometimes a bit of rewriting is needed, and sometimes there are no changes at all.

How do I win a copy of the book?

When the porting is done, I randomly pick three of the ported questions that were not ported by me, and the ones who ported those questions get a copy each. If I draw the same person several times, I draw again until I have three separate winners.

Let’s get started!

References don’t have top-level cv-qualifiers


Sometimes when reading about C++, for instance about template argument deduction, the term “top-level cv-qualifiers” comes up. I just spent an unreasonable amount of time being puzzled by something because I didn’t understand that references don’t have top-level cv-qualifiers. This post will hopefully help the next person (hi, next-year Anders) not make the same mistake.

Looking at

const int&

, I assumed without even thinking about it that the const here was the top-level one. It is not.

First, what’s a cv-qualifier? It’s const, volatile or both. Let’s just use const as an example for this article.

Then, what’s a top-level cv-qualifier? The best way to explain is with an example, and the best example is a pointer. There are two levels of constness to a pointer, the constness of the pointer itself, and the constness of what it’s pointing to.

Given const int *, a non-const pointer to const int, we can visualise it as

pointer (the * part)
to
const int (the const int part)

And given const int * const, a const pointer to const int, we can visualise it as

const pointer (the * const part)
to
const int (the const int part)

(And so on, you can imagine how it looks for pointers to non-const int.) The top-level cv-qualifier is the one on the top level, the cv-qualifier on the pointer itself.

Now, how does this look for references?

Given const int&, a reference to const int, we can visualise it as

reference (the & part)
to
const int (the const int part)

But there’s no such thing as a const reference! Constness applies to the object itself, and a reference is not an object, just an alternative name for an existing object. So there is no such thing as a const int& const, i.e. there’s no such thing as

const reference (the & const part)
to
const int (the const int part)

Which means, references don’t have top-level cv-qualifiers. The standard even has an example:

Example: The type corresponding to the type-id const int& has no top-level cv-qualifiers.

[basic.type.qualifier]

This is by the way a somewhat recent addition, until this Core Language Defect Report was resolved in 2014, the term “top-level cv-qualifier” was never actually defined in the standard.

Why we probably shouldn’t have constexpr conditional operator


The idea

I had a great idea. We have constexpr if, but no constexpr conditional operator. Time for a proposal?

Since we can do stuff like this:

if constexpr(cond) { foo; } else { bar;}

Wouldn’t it be cool if we could also do

cond ? constexpr foo : bar;

My motivation was that I had a std::variant visitor that was identical for all types except one. So instead of writing an overload set for std::visit, it was simpler to have one common lambda with a conditional inside. Something like this, which returns “int” for int and “other” for all other types in the variant:

std::visit([]<typename T>(T value) {
        if constexpr(std::is_same_v<int, T>)
        {
            return "int";
        }
        else
        {
            return "other";
        }
    },
    my_variant);

It would be nicer to write it like this with a conditional operator, but now we can’t use constexpr.

std::visit([]<typename T>(T value) {
        return std::is_same_v<int, T> ? "int" : "other";
    },
    my_variant);

So I had the idea of constexpr conditional operator, so I could write my lambda something like this:

std::visit([]<typename T>(T value) {
         return std::is_same_v<int, T> ? constexpr "int" : "other";
    },
    my_variant);

In this case, constexpr doesn’t actually make much of a difference. std::is_same_v is a constant expression no matter if you use the constexpr keyword or not, so the compiler optimises it equally well in either case. But at least we verify that the condition is actually a constant expression. If we mess this up, we get a compiler error.

But the most important advantage of constexpr if is that each branch only need to compile if that branch is taken at compile time. So you can do for instance

template<typename T>
int f(T t) {
    if constexpr(std::is_same_v<T, std::string>)
        return t.size();
    else
        return t;
}

and this will work both for int and std::string, even if the first branch wouldn’t compile for an int and the second wouldn’t compile for std::string. Remove constexpr above, and you’re in trouble.

As it turns out, this is exactly why constexpr conditional operator might not be such a good idea! Thanks to Daniela Engert who pointed this problem out to me.

The problem

if is a statement, it doesn’t have a type. The conditional operator however is an expression, and has a type!

You can’t assign the result of an if statement to something, it doesn’t have a type or result in a value. The conditional operator does however. And the type of the conditional operator is determined by a set of rules which find a common type for the two branches. For instance:

auto result = false ? std::optional<int>{2} : 0;

The two branches have the types std::optional<int> and int, respectively. The compiler now has to figure out what the type of the expression should be, by trying to form implicit conversion sequences from the first to the other, and vice versa. See [expr.cond] for details. Since one can implicitly convert an int to a std::optional<int>, but not vice versa, the type of the full conditional expression (and thus the type of result) is std::optional<int>.

If we introduced something like ? constexpr here, with the same semantics as if constexpr, suddenly one of the branches would be discarded. And we’d have to do that, since the whole point is that the branch not taken usually doesn’t even compile. So in the case above, the first branch would be discarded, and we’d only be left with the literal 0 which has type int. Left with only the int to deduce a type from, the type of the full conditional expression would now be int instead of std::optional<int>. And Daniela’s argument, which I agree with, is that it could be surprising if the type of an expression changed just by introducing or removing constexpr.

In comparison, remember that an if statement doesn’t result in a value, and doesn’t even have a type. If you want to do the same with an if, you first have to define the result variable, and there’s no way to do that upfront without explicitly deciding on its type:

std::optional<int> result;
if constexpr (false)
    result = std::optional<int>{2};
else
    result = 0;

Notice here that the type of the value we assign to result is still different based on the constexpr condition, but now there’s no surprise, the resulting type is always the same. Both branches have to result in a type implicitly convertible to std::optional<int>, if they’re ever instantiated.

A counter argument?

There is one final point that needs to be mentioned, where the types of two if constexpr branches actually do influence type deduction. This can happen when you have a function with an auto return type, and you return from inside the if constexpr. Here’s a demonstration with a function template, but it can also happen for regular functions:

template<bool b>
auto f()
{
    if constexpr (b)
        return std::optional<int>{2};
    else
        return 0;
}

The return type of f<true> is std::optional<int>, and the return type of f<false> is int. Isn’t this the same problem we just used to argue against constexpr conditional operator? It’s similar, but not the same. The big difference is that removing constexpr in this example doesn’t change the deduced type, it rather causes a compilation error. This is due to dcl.spec.auto#8, which is very strict about all non-discarded return statements having the same type, not just types that can be implicitly converted to a common type:

If a function with a declared return type that contains a placeholder type has multiple non-discarded return statements, the return type is deduced for each such return statement. If the type deduced is not the same in each deduction, the program is ill-formed.

dcl.spec.auto#8

Conclusion

For constexpr conditional operator, adding/removing constexpr could change a deduced type, which could be surprising. For constexpr if, this doesn’t happen.

What do you think? Should we have constexpr conditional operator or not?

The difference between no move constructor and a deleted move constructor


It’s easy to think that deleting the move constructor means removing it. So if you do MyClass(MyClass&&) = delete , you make sure it doesn’t get a move constructor. This is however not technically correct. It might seem like a nitpick, but it actually gives you a less useful mental model of what’s going on.

First: When does this matter? It matters for understanding in which cases you’re allowed to make a copy/move from an rvalue.

Here are some examples of having to copy/move an object of type MyClass:

MyClass obj2(obj1);
MyClass obj3(std::move(obj1));

MyClass obj4 = obj1;
MyClass obj5 = std::move(obj1);

return obj1;
return std::move(obj1);

These are examples of “direct initialization” (the former two) and “copy initialization” (the latter four). Note that there is no concept of “move initialization” in C++. Whether you end up using the copy or the move constructor to initialize the new object is just a detail. For the rest of this post, let’s just look at copy initialization, direct initialization works the same for our purposes. In any case you create a new copy of the object, and the implementation uses either the copy or the move constructor to do so.

Let’s first look at a class NoMove:

struct NoMove
{
    NoMove();
    NoMove(const NoMove&);
};

This class has a user-declared copy constructor, so it doesn’t automatically get a move constructor. From the C++ standard https://timsong-cpp.github.io/cppwp/n4659/class.copy:

If the definition of a class X does not explicitly declare a move constructor, a non-explicit one will be implicitly declared as defaulted if and only if

– X does not have a user-declared copy constructor,
– (…)

So this class doesn’t have a move constructor at all. You didn’t explicitly declare one, and none got implicitly declared for you.

On the other hand, let’s see what happens if we explicitly delete the move constructor:

struct DeletedMove
{
    DeletedMove();
    DeletedMove(const DeletedMove&);
    DeletedMove(DeletedMove&&) = delete;
};

This is called ” a deleted definition”. From the C++ standard: https://timsong-cpp.github.io/cppwp/n4659/dcl.fct.def.delete

A function definition of the form:
(…) = delete ;
is called a deleted definition. A function with a deleted definition is also called a deleted function.

Importantly, that does not mean that its definition has been deleted/removed and is no longer there. It means that is has a definition, and that this particular kind of definition is called a “deleted definition”. I like to read it as “deleted-definition”.

So our NoMove class has no move constructor at all. Our DeletedMove class has a move constructor with a deleted definition.

Why does this matter?

Let’s first look at a class with both a copy and a move constructor, and how to copy-initialize it.

struct Movable
{
    Movable();
    Movable(const Movable&);
    Movable(Movable&&);
};

Movable movable;
Movable movable2 = movable;

When initializing movable2, we need to find a function to do that with. A copy constructor would do nicely. And since we do have a copy constructor, it indeed gets used for this.

What if we turn movable into an rvalue?

Movable movable2 = std::move(movable);

Now a move constructor would be great. And we do have one, and it indeed gets used.

But what if we didn’t have a move constructor? That’s the case with our class NoMove from above.

struct NoMove
{
    NoMove();
    NoMove(const NoMove&);
};

This one has a copy constructor, so it doesn’t get a move constructor. We can of course still make copies using the copy constructor:

NoMove noMove;
NoMove noMove2 = noMove;

But what happens now?

NoMove noMove;
NoMove noMove2 = std::move(noMove);

Are we now “move initializing” noMove2 and need the move constructor? Actually, we’re not. We’re still copy-initializing it, and need some function to do that task for us. A move constructor would be great, but a copy constructor would also do. It may be less efficient, but of course you’re allowed to make a copy of an rvalue.

So this is fine, the code compiles, and the copy constructor is used to make a copy of the rvalue.

What happened behind the scenes in all the examples above, is overload resolution. Overload resolution looks at all the candidates to do the job, and picks the best one. In the cases where we initialize from an lvalue, the only candidate is the copy constructor. We’re not allowed to move from an lvalue. In the cases where we initialize from an rvalue, both the copy and the move constructors are candidates. But the move constructor is a better match, as we don’t have to convert the rvalue to an lvalue reference. For Movable, the move constructor got selected. For NoMove, there is no move constructor, so the only candidate is the copy constructor, which gets selected.

Now, let’s look at what’s different when instead of having no move constructor, we have a move constructor with a deleted definition:

struct DeletedMove
{
    DeletedMove();
    DeletedMove(const DeletedMove&);
    DeletedMove(DeletedMove&&) = delete;
};

We can of course still copy this one as well:

DeletedMove deletedMove2 = deletedMove;

But what happens if we try to copy-initialize from an rvalue?

DeletedMove deletedMove2 = std::move(deletedMove);

Remember, overload resolution tries to find all candidates to do the copy-initialization. And this class does in fact have both a copy and a move constructor, which are both candidates. The move constructor is picked as the best match, since again we avoid the conversion from an rvalue to an lvalue reference. But the move constructor has a deleted definition, and the program does not compile. From the C++ standard: https://timsong-cpp.github.io/cppwp/n4659/dcl.fct.def.delete#2:

A program that refers to a deleted function implicitly or explicitly, other than to declare it, is ill-formed. [ Note: This includes calling the function implicitly or explicitly (…) If a function is overloaded, it is referenced only if the function is selected by overload resolution.

The function is being called implicitly here, we’re not manually calling the move constructor. And we can see that this applies because overload resolution selected to use the move constructor with the deleted definition.

So the differences between not declaring a move constructor and defining one as deleted are:

  • The first one does not have a move constructor, the second one has a move constructor with a deleted definition.
  • The first one can be copy-initialized from an rvalue, the second can not.

If you enjoyed this post, you can subscribe to my blog, or follow me on Twitter.

No-one knows the type of char + char


Quick quiz! Given the following:

void f(unsigned int);
void f(int);
void f(char);

Which overload gets called by the following?

char x = 1;
char y = 2;
f(x + y);

Alternatives:

  1. f(unsigned int)
  2. f(int)
  3. f(char)
  4. No-one knows the type of char + char

If you answered 4), congratulations! And if you answered 2), maybe you tried the code on your own computer? Most people will get f(int) when they try this code, but this is actually not specified by the standard. The only thing we know for sure is that it’s not 3), f(char)!

Let’s have a look at what’s going on:

Before being passed to operator +, the operands (x and y) go through a conversion. [expr.add]§8.7¶1:

The usual arithmetic conversions are performed for operands of arithmetic or enumeration type.

What are “the usual arithmetic conversions”?

[expr]§8¶11:

Many binary operators that expect operands of arithmetic or enumeration type cause conversions and yield result types in a similar way. The purpose is to yield a common type, which is also the type of the result. This pattern is called the usual arithmetic conversions, which are defined as follows:
– [a bunch of rules for floats, enums etc]
– Otherwise, the integral promotions (7.6) shall be performed on both operands

So both chars go through integral promotions. Those are defined in [conv.prom]§7.6¶1:

A prvalue of an integer type other than bool, char16_t, char32_t, or wchar_t whose integer conversion rank (7.15) is less than the rank of int can be converted to a prvalue of type int if int can represent all the values of the source type; otherwise, the source prvalue can be converted to a prvalue of type unsigned int.

So a char gets converted to an int if int can fit all possible values of a char. If not, they get converted to unsigned int. But any char should fit in an int, right? As it turns out, that’s not necessarily the case.

First, int could actually be the same size as char. [basic.fundamental]§6.9.1¶2:

There are five standard signed integer types : “signed char”, “short int”, “int”, “long int”, and “long long int”. In this list, each type provides at least as much storage as those preceding it in the list.

Note that it says “at least as much storage”, it doesn’t have to be more. So for instance you could have an sixteen bit system where both char and int are sixteen bits.

Second, char can be either signed or unsigned, it’s up to the implementation: [basic.fundamental]§6.9.1¶1:

It is implementation-defined whether a char object can hold negative values.

int is signed, so if char is also signed, all possible values of char will fit in an int. However, if char is unsigned, and int and char is the same size, char can actually hold larger values than int!

Let’s see an example. If char and int are both sixteen bits, int (which is always signed) can hold [-32768, 32767]. If char is signed, it can also hold [-32768, 32767], and any char fits in an int. However, if char is unsigned, it can hold [0,65535], half of which fall outside the range of int!

In the former case, chars get promoted to ints, but in the latter case, chars get promoted to unsigned ints before being summed.

So in practice, most systems will call f(int), but some might call f(unsigned int), and they would both be confirming to the standard.

If you enjoyed this post, you can subscribe to my blog, or follow me on Twitter.