Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Standard or idiomatic empty object in C++

Tags:

c++

c++20

I often need to define an empty object in C++, which is typically a structure without any members or methods:

struct Dummy {};

Reincarnations of the same idea include classes like nill_t, null_t, Empty, None etc.

I was wondering if there is a standard library object that fits this description (empty class, used as tag for "nothing" or "empty") or a canonical way to define it (proper name etc). One idea would be to use std::false_type but unfortunately this class is far from empty.


Edit:

Ideally I'd use the Standard None type as a (n empty) base to have both the rich name description and hide implementation (or use it as is, where it makes sense)

struct Dummy : std::none {};
struct Noop  : std::none {};

Having the language take care of intricasies of "nothingness" like the one @Barry mentions in his answer, or e.g. providing an equality operator that always returns false (like NaN) or whatever the language considers "proper behavior" for "nothing" would be nice.

The question merely asks whether there's a standard None or Empty or Nothing type, I never suggested obfuscating the code by using it where it doesn't make sense.

like image 289
Lorah Attkins Avatar asked May 19 '21 16:05

Lorah Attkins


People also ask

Is Empty object same as null?

The main difference between null and empty is that the null is used to refer to nothing while empty is used to refer to a unique string with zero length.

Are types objects in c++?

The following entities are not objects: value, reference, function, enumerator, type, non-static class member, template, class or function template specialization, namespace, parameter pack, and this.


Video Answer


3 Answers

The C++ standard library actually has a large number of "empty" types. std::monostate, std::nullopt_t, the std::in_place_t tag types, etc. They all have different names because they are used for different purposes.

You should not have one all-purpose "empty" type, because seeing the name there will tell you what the code is doing. std::optional<T>(std::in_place, ...) communicates a lot more about what is going on than std::optional<T>(std::monostate{}, ...).

Indeed, if a single type were used, you wouldn't be able to differentiate between std::optional<T>(std::nullopt) and std::optional<T>(std::in_place). These are two very different function calls.

The name you use should be whatever makes the code which uses it readable.

As for canonical conventions, the C++ standard uses certain conventions based on how you're using it. Types that the user is expected to type directly into their code, like std::monostate, are named normally. You're expected to create variants that use monostate in the type.

Tag types, used to select constructor or operator() overloads, will typically end in _t. But they also have an inline constexpr variable defined for them that doesn't end in _t. For example, std::nullopt_t is the name of the type, while std::nullopt is the name of a variable of that type.

This is particularly important, as std::nullopt_t's cannot directly be constructed by the user. You can copy std::nullopt instances around, but you cannot create a std::nullopt_t instance directly.

That last bit is something the standard is a bit inconsistent about. Most tag types don't have this requirement; you can invoke std::in_place_t{} all you like. But std::nullopt_t does.


Examples from your comment:

as the default tag in struct templates, as the default tag in function templates

I don't know what a "default tag" is in this context, but I would probably call it default_t. Since that spells out the meaning of the thing.

as the "no output" tag in a processing pipeline

I would spell this void or if that's not an option, drop or discard.

as the equivalent of Python's None in some generic code

That already has a name: nullptr. If it is a non-pointer that may or may not be present, it's spelled std::optional<T>. And if you really are in generic code, std::nullopt itself works as a way to indicate that the user conceptually provided an empty optional.

These are different uses with different interpreted meaning. Therefore, they should not have the same name even if they're all implemented as equivalent types.

like image 175
Nicol Bolas Avatar answered Oct 08 '22 01:10

Nicol Bolas


The best practice is to do it this way:

struct Dummy { explicit Dummy() = default; };

Which you can then create an instance of:

inline constexpr Dummy dummy;

The advantage of this is that Dummy is not an aggregate (in C++20), which prevents this from compiling:

void f(Dummy);

f({});      // error
f(Dummy{}); // ok
f(dummy);   // ok

Which is important because the point of tag types is visibility, and {} is... not that.


I was wondering if there is a standard library object that fits this description

There are quite a few standard library empty tag types (e.g. std::nullopt_t), but if you need a tag type, you need it for a particular reason and reusing an existing tag just to avoid creating a new type doesn't seem like a good tradeoff. I mean, use std::nullopt_t if that makes sense for what you're doing (perhaps you're doing something equivalent to optional and so that's a reasonable tag to use for this problem) but don't just use std::nullopt_t because... you need a tag and that's available.

like image 33
Barry Avatar answered Oct 08 '22 01:10

Barry


C++17 introduced std::monostate. It is meant to be used with std::variant but also fits nicely "empty class, used as tag for "nothing" or "empty"".

It comes with a couple of related free functions that enable most common comparisons, but otherwise is nothing more than:

struct monostate { };
like image 2
463035818_is_not_a_number Avatar answered Oct 07 '22 23:10

463035818_is_not_a_number