10

You remember from your undergrad lectures in algorithms that it's very handy to have a concept of Nil, which anything can be assigned to or compared with. (By the way I never did an undergrad in computer science.) In Python we can use None; in Scala there is Nothing (which is a subobject of everything if I'm understanding 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 Pattern, but my current impression is that most of you would wince at the very thought of that.

Or we could define a global or a static.

My problem is that in either of these cases, I can't think of a way of being able to assign any variable of any type to Nil, or being able to compare any object of any type with Nil. Python's None is useful because Python is dynamically typed; Scala's Nothing (not to be confused with Scala Nil, which means empty list) solves this elegantly because Nothing is a subobject of everything. So is there an elegant way of having Nil in C++?

Community
  • 1
  • 1
Ray
  • 7,833
  • 13
  • 57
  • 91

6 Answers6

11

No, there is no such thing as a universal nil value in C++. Verbatim, C++ types are unrelated and do not share common values.

You can achieve some form of shareable values by using inheritance, but you must do so explicitly, and it can only be done for user-defined types.

Angew is no longer proud of SO
  • 167,307
  • 17
  • 350
  • 455
  • 2
    The closest to that concept would be `nullptr` but that is specific to pointers – Cory Kramer Apr 09 '15 at 18:32
  • Does that mean that there is no way of having a universal `Nil` even if I implement it manually somehow (and I don't know how)? – Ray Apr 09 '15 at 18:51
  • 5
    @Ray Yeah, there is no way to have a truley universal Nil. At best, you can have a Nil that works for some subset of types. –  Apr 09 '15 at 18:56
  • That said, I would remove "By default", since there's no way to change this. – Kerrek SB Apr 09 '15 at 19:10
  • @KerrekSB: inheritance. – Cheers and hth. - Alf Apr 09 '15 at 19:11
  • @Cheersandhth.-Alf: That isn't what "default" means, though. The statement gains no further power by adding "by default". I could settle for "in general", but I'd rather just strike it. And inheritance doesn't magically equate different types; rather, it makes one object contain a subobject of another type, and then you can compare subobjects, and some implicit conversions exist. – Kerrek SB Apr 09 '15 at 19:13
  • @Cheersandhth.-Alf: Now it says less than before. `struct A{};` and `struct B{};` are also unrelated, and they're not built-in. Just remove that part entirely. – Kerrek SB Apr 09 '15 at 19:58
  • Uhm, @Angew, please look at it? – Cheers and hth. - Alf Apr 09 '15 at 21:38
  • @Cheersandhth.-Alf How about this? I hope it's still "to the point" enough. – Angew is no longer proud of SO Apr 10 '15 at 09:03
  • @Angew: Looks fine to me (except a typo, a missing "r" in "user"). ;-). – Cheers and hth. - Alf Apr 10 '15 at 09:35
  • But there *is* a universal `nil` value in C++, at least for pointers: `nullptr`. At least as far as comparing them goes. – Barry Apr 10 '15 at 11:10
  • 1
    @Barry: "This statement is universally true, at least when restricted to cases where it is true"? – Kerrek SB Apr 10 '15 at 13:40
  • @Angew: Hm, I think the added paragraph just made it more cluttered. The initial answer was good. Inheritance-related types don't share common values, either; rather, it's a matter of one type containing one or multiple subobjects of the other type. But I don't think that helps or is pertinent to the question. – Kerrek SB Apr 10 '15 at 13:42
  • @KerrekSB While inheritance does not make types share common values, it *does* make them related. That's why I had the "By default" in the original version, and the "verbatim" in this one. Types are unrelated, unless you explicitly make them such. I''d be happy to ditch the second para, but I need *some* form of the "by default" in there. – Angew is no longer proud of SO Apr 10 '15 at 13:55
  • @KerrekSB That sentence in the answer is literally true, yes. But it is incomplete. – Barry Apr 10 '15 at 14:22
  • @Angew: Ah, OK, the "related" part needs addressing. How about this: "Types in C++ do not share common values and are generally unrelated, although conversions exist between certain types (e.g. between arithmetic types, or between base and derived classes). – Kerrek SB Apr 10 '15 at 14:38
10

There are two related concepts what you describe as Nil: the unit type and the option type.

Unit Type

This is what NoneType is in Python and nullptr_t is in C++, it's just a special type that has a single value that conveys this specific behavior. Since Python is dynamically typed, any object can be compared against 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 need) such a feature, but all the ML family do. This is implementable in C++ via boost::optional. This is a type-safe encapsulation of the idea that a particular object can maybe have a value, or not. This idea is more expressive in the functional family than it is in C++ though:

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

Although easy enough to understand in C++ too, once you see it:

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

The advantage here is that the option type is a value type, and you can easily express a "value" of nothing (e.g. your optional<int*> can store nullptr as a value, as separate from "Nil"). Also this can work with any type, not just pointers - it's just that you have to pay for the added functionality. An optional<T> will be larger than a T and be more expensive to use (although only by a little, although that little could matter a lot).

A C++ Nil

We can combine those ideas together to actually make a Nil in C++, at least one that works with pointers and optionals.

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 anything that implements operator!

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

, but it would be better to limit ourselves to the clear-cut cases and I like the explicitness that pointers and optionals give you)

Thus:

int* i = 0;
std::cout << (i == Nil); // true
i = new int(42);
std::cout << (i == Nil); // false
Barry
  • 286,269
  • 29
  • 621
  • 977
3

C++ follows the principle that you don't pay for what you don't use. For example, if you want a 32-bit integer to store the full range of 32-bit values and an additional flag about whether it is Nil, this will take more than 32-bits of storage. Certainly you can create clever classes to represent this behaviour, but it's not available "out of the box".

Neil Kirk
  • 21,327
  • 9
  • 53
  • 91
3

You can't have this in C++ without having to rely on a container that encapsulate your type (such as boost::optional).

Indeed, there's a combination of reasons, as C++ is a:

  • statically typed language: once you define a variable with a type, it sticks to that type (meaning you can't assign a value None which isn't among what each type can represent, which you could do in other, dynamically typed, languages like Python)
  • language with built-in[1] types and no common base object for its types: you can't have the semantics of None plus "All the values that this type could represent otherwise" in a common object for all your types.

[1] which aren't like Java built-in types that all have their corresponding class inheriting from java.lang.Object.

JBL
  • 12,588
  • 4
  • 53
  • 84
  • 1
    Static typing goes even further than what you describe. Not only variables have a statically known type, but *expressions* too have a statically determined type, and almost all objects do, too (with one exception). – Kerrek SB Apr 10 '15 at 08:37
2

In most of those languages, variables are not implemented as the name of objects, but rather as handles to objects.

Such handles can be set to "hold nothing" with very little additional overhead. This is analogous to a C++ pointer, where the nullptr state corresponds to "pointing to nothing".

Often such languages do full on garbage collection. Both garbage collection and forced indirect referencing of data have significant performance hits in practice, and they restrict what kind of operations you can do.

When you use a variable in such languages, you have to first 'follow the pointer' to get at the real object.

In C++, a variable name typically refers to the actual object in question, not a reference to it. When you use a variable in C++, you directly access it. An extra state (corresponding to "nothing") would require extra overhead in every variable, and one of the principles of C++ is that you don't pay for what you use.

There are ways of making a "nullable" data type in C++. These vary from using raw pointers, using smart pointers, or using something like std::experimantal::optional. All of them have a state for "there is nothing there", usually detectable by taking the object and evaluating it in a bool context. To access the actual data (assuming it exists), you use unary * or ->.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • `nullptr` is a keyword, not a state, and the type of the value it designates is not a pointer type. I'd say "null state" or "null value"; the standard calls it "null pointer value". – Kerrek SB Apr 10 '15 at 08:39
  • @KerrekSB By `nullptr` state, I mean the equivalence class of each pointer type containing `nullptr`. Much like the `0` of the integers is distinct from the `0` of the reals or complex or quaternions or vectors or matrices, calling all of them the `0` state of their respective types is reasonably clear (especially if there is a conversion from `0` in each case). – Yakk - Adam Nevraumont Apr 10 '15 at 13:26
-1

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

If it's not a pointer type it must be there. For particular values you could define special "I-don't-exist" values, but there is no universal solution.

If it may not exist your choices are:

  • point to it and use nullptr indicate a nonexistent value, or
  • keep a separate flag to indicate existence or non-existence (which is what boost::optional does for you), or
  • define a special value for this particular usage to represent a non-existent value (note this is not always possible.)
Dale Wilson
  • 9,166
  • 3
  • 34
  • 52