The concept of `Nil` in C ++

You remember from your lecture lectures in algorithms that it is very convenient to have a Nil concept with which everything can be assigned or compared with it. (By the way, I never studied computer science.) In Python, we can use None ; Nothing exists in Scala (which is a subobject of everything, if I understand it correctly). But my question is: how can we have Nil in C ++? Below are my thoughts.

We could define a singleton object using the Singleton design template , but my current impression is that most of you startled at the very thought of it.

Or we could define global or static.

My problem is that in any of these cases I cannot think of a way to assign any variable of any type to Nil or the ability to compare any object of any type to Nil , Python None is useful because Python is dynamically typed; Scala Nothing (not to be confused with Scala Nil , which means an empty list) solves this gracefully because Nothing is a subobject of everything. So, is there an elegant way to have Nil in C ++?

+7
c ++ null
source share
6 answers

No, in C ++ there is no universal nil value. The types Verbatim, C ++ are not related and do not have common values.

You can achieve a certain form of common values ​​using inheritance, but you must do this explicitly, and this can only be done for custom types.

+9
source share

There are two related concepts that you describe as Nil : device type and option type .

Device type

This is because NoneType is in Python and nullptr_t is in C ++, it is just a special type that has a single value that conveys this particular behavior. Since Python is dynamically typed, any object can be compared to None , but in C ++ we can only do this for pointers:

 void foo(T* obj) { if (obj == nullptr) { // Nil } } 

This is semantically identical to python's:

 def foo(obj): if foo is None: # Nil 

Option Type

Python does not have (or does not need) such a function, but the entire ML family. This can be implemented in C ++ via boost::optional . This is a safe type of encapsulation of the idea that a particular object may or may not matter. This idea is more expressive in a functional family than in C ++:

 def foo(obj: Option[T]) = obj match { case None => // "Nil" case case Some(v) => // Actual value case } 

Although fairly easy to understand in C ++, as soon as you see this:

 void foo(boost::optional<T> const& obj) { if (obj) { T const& value = *obj; // etc. } else { // Nil } } 

The advantage is that the type of the parameter is the type of the value, and you can easily express the "value" of nothing (for example, your optional<int*> can store nullptr as a value, separate from "Nil"). In addition, it can work with any type, and not just with pointers - you just have to pay for the added functionality. optional<T> will be larger than a T and will be more expensive to use (albeit slightly, although this may not matter much).

A C ++ Nil

We can combine these ideas together to actually make Nil in C ++, at least the one that works with pointers and options.

 struct Nil_t { }; constexpr Nil_t Nil; // for pointers template <typename T> bool operator==(const T* t, Nil_t ) { return t == nullptr; } template <typename T> bool operator==(Nil_t, const T* t ) { return t == nullptr; } // for optionals, rhs omitted for brevity template <typename T> bool operator==(const boost::optional<T>& t, Nil_t ) { return !t; } 

(Note that we can even generalize this to everything that operator! implements operator!

 template <typename T> bool operator==(const T& t, Nil_t ) { return !t; } 

but it would be better to confine ourselves to clear cases, and I like the explanations that pointers and advanced options indicate)

Thus:

 int* i = 0; std::cout << (i == Nil); // true i = new int(42); std::cout << (i == Nil); // false 
+9
source share

C ++ follows the principle that you do not pay for what you do not use. For example, if you want a 32-bit integer to preserve the full range of 32-bit values ​​and an additional flag on whether it is Nil, it will take more than 32 bits of memory. Of course, you can create smart classes to represent this behavior, but it is not available out of the box.

+3
source share

You cannot have this in C ++ without relying on a container that encapsulates your type (e.g. boost::optional ).

Indeed, there is a combination of reasons, since C ++ is:

  • statically typed language: as soon as you define a variable with a type, it sticks to this type (this means that you cannot assign a value of None , which does not apply to what each type can represent, which you could do in others, dynamically typed languages ​​like Python)
  • with built-in [1] types and without a common base object for its types: you cannot have the semantics None plus "All values ​​that this type could represent otherwise" in a common object for all your types.

[1], which do not look like Java built-in types, all of which have their own class inheriting from java.lang.Object .

+3
source share

In most of these languages, variables are not implemented as the name of objects, but rather as object descriptors.

Such handles can be configured to “contain nothing” with very little extra overhead. This is similar to a C ++ pointer, where the nullptr state corresponds to "point to nothing."

Often these languages ​​fill the garbage collection. Both garbage collection and forced indirect data binding have significant results in practice, and they limit what operations you can perform.

When you use a variable in such languages, you first need to "follow the pointer" to get the real object.

In C ++, a variable name usually refers to the actual object, not to it. When you use a variable in C ++, you access it directly. An additional state (corresponding to “nothing”) will require additional overhead in each variable, and one of the principles of C ++ is that you do not pay for what you use.

There are ways to create null data types in C ++. They range from using source pointers, using smart pointers, or using something like std::experimantal::optional . All of them have a state of "there is nothing there," usually found by taking an object and evaluating it in the context of a bool . To access the actual data (provided that they exist), you use unary * or -> .

+2
source share

In C ++ 11, there is a nullptr value that can be assigned to any type of pointer.

If this is not a pointer type, it should be there. For certain values, you can define special values ​​of "I-don't-exist", but there is no universal solution.

If it may not exist, your options are:

  • point to it and use a nullptr value indicating a nonexistent value, or
  • keep a separate flag to indicate existence or non-existence (which boost::optional means to you), or
  • define a special value for this particular use to represent a non-existent value (note that this is not always possible.)
-one
source share

All Articles