T* Makes for a Dejected Non-compulsory

84
T* Makes for a Dejected Non-compulsory

Each time the premise of an no longer compulsory reference comes up, inevitably any individual will bring up the purpose that we don’t wish to toughen no longer compulsory because of we already obtain in the language a wonderfully fair straight away now not compulsory reference: T*.

And these two kinds are superficially pretty identical, which makes the argument facially realistic. Certainly, no longer compulsory would definitely be conducted to acquire this form:

template <> struct no longer compulsory {
    Tstorage = nullptr;

    explicit constexpr operator bool() const { return storage; }
    constexpr auto operator*() const -> T& { return *storage; }
    constexpr auto operator->() const -> T{ return storage; }
};

Now not easiest obtain T* and no longer compulsory obtain the identical representation, however they even obtain the identical meaning for his or her contextual conversion to bool, their dereference operator, and their arrow operator.

The reason for this post is to show that, despite these similarities, T* is merely no longer a resolution for no longer compulsory. There are several causes for this, however I’ll originate up with what’s overwhelmingly the strongest.

§ no longer compulsory favorable works, even for U=T&

Let’s recount you are looking for to jot down an algorithm that given any range returns the first element in it (the purpose right here isn’t explicit to Ranges, however Ranges does obtain for fair exact, familiar examples). If we now obtain a precondition that the diversity is no longer empty, we are able to also write that algorithm this fashion:

template 
constexpr auto front(R&& r) -> ranges::range_reference_t {
    return *ranges::originate up(r);
}

Now, despite the name, the reference form of a range need no longer in fact be a language reference. If I passed in a vector, I’d get abet an int& that refers back to the first element. But if I passed in one thing esteem views::iota(0, 10), I’d get abet an int (no longer a reference) with cost 0.

Let’s recount as an different of getting a precondition that the diversity is non-empty, I are looking for to handle that case too. I are looking for to jot down a total feature as an different of a partial feature. The very most realistic blueprint to acquire that is both return some cost (if I will) or no cost (if I will’t). That’s an no longer compulsory, that’s what it’s for: to handle returning one thing or nothing.

The style to spell that is:

template 
constexpr auto try_front(R&& r)
    -> no longer compulsory>
{
    auto first = ranges::originate up(r);
    auto closing  = ranges::end(r);
    if (first != closing) {
        return *first;
    } else {
        return nullopt;
    }
}

This would return an no longer compulsory for the views::iota case, which is okay. However it can presumably per chance per chance strive to return an no longer compulsory for the vector case, since the reference kind is int& there. This doesn’t work with std::no longer compulsory.

Now, the argument goes that we don’t need no longer compulsory to exist because of we now obtain int*. So let’s strive to metaprogram our blueprint out of this box:

template 
the expend of workaround = conditional_t<
    is_reference_v,
    add_pointer_t, // add_pointer_t is intoptional>;

template 
constexpr auto try_front(R&& r)
    -> workaround>
{
    auto first = ranges::originate up(r);
    auto closing  = ranges::end(r);
    if (first != closing) {
        return *first;
    } else {
        return {};
    }
}

Does this work? No, it doesn’t.

Initially there’s the shriek that it’s doable that even spelling no longer compulsory is sick-shaped (which I mediate it is allowed for the implementation to acquire). So the categorical implementation of workaround would need to be more complicated, however let’s favorable ignore that piece.

The motive that this doesn’t work is that int* in fact has many tremendously varied semantics from no longer compulsory, and a form of importantly varied semantics is: building. An no longer compulsory may presumably per chance per chance be constructible from an lvalue of kind int, however an int* is no longer – pointers require explicit building syntax, whereas references obtain implicit building syntax.

In uncover to return an int* for the vector case, I will’t obtain *originate up(v), I in fact must obtain &*originate up(v). But I will’t obtain that for the views::iota trigger, because of there dereferencing the iterator supplies me a prvalue. There I obtain must obtain *originate up(v).

Meaning the true implementation would must behold more esteem:

template 
the expend of workaround = mp_eval_if_c<
    is_reference_v,
    add_pointer_t,
    optional, T>;

template 
constexpr auto try_front(R&& r)
    -> workaround>
{
    auto first = ranges::originate up(r);
    auto closing  = ranges::end(r);
    if (first != closing) {
        if constexpr (std::is_reference_v>) {
            return &*first;
        } else {
            return *first;
        }
    } else {
        return {};
    }
}

Here’s already, I mediate, in fact pretty unpleasant.

Now let’s strive to make expend of it! If I are looking for to deal with the first element in a range or provide a default cost to handle the empty case (which is a extraordinarily classic thing to are looking for to acquire, take note what number of cases you’ve presumably accomplished this in the case of scheme lookup the set you’ve gotten it == m.end() ? one thing : it->2nd), I could presumably per chance per chance strive to jot down:

int cost = try_front(r).value_or(-1);

And that works. For the views::iota case anyway, since try_front(views::iota(~)) will give me an no longer compulsory.

But passing in a vector right here received’t bring together, because of we made that case return an int* as an different of an no longer compulsory and there’s now not always a value_or member on a pointer (or, indeed, every other roughly member both). We’d wish to produce but every other workaround right here.

Which… we are able to. It’s no longer that onerous to jot down a non-member value_or that works for every no longer compulsory and T*:

template 
constexpr auto value_or(P&& ptrish, U&& dflt) {
    return ptrish ? *FWD(ptrish) : FWD(dflt);
}

And now we are able to as an different write:

int cost = N::value_or(try_front(r), -1);

Up to now, we’ve made ourselves obtain a worse implementation of try_front() in uncover to return T*, which in flip ended in a worse implementation of the utilization of try_front to handle the T* case. At this point you may presumably per chance per chance presumably bring up one thing esteem the pipeline operator (|>, which may presumably per chance well enable try_front(r) |> N::value_or(-1), which as a minimum has the identical form of the specified expression) or unified feature name syntax (which, if you clutch the genuine version, as a minimum helps you to jot down try_front(r).N::value_or(-1), assuming an excellent name is supported there). But we restful obtain the shriek the set we now must write our obtain value_or() which has to handle every no longer compulsory and T*.

Let’s behold at a varied instance. In my CppNow 2021 talk and again in my recent CPPP 2021 talk (video pending), I prove what the Rust iterator model appears to be like esteem if obtain been to place in force it in C++. In Rust, an Iterator is:

trait Iterator {
    kind Merchandise;
    fn subsequent(&mut self) -> Option;
}

which in C++20 ideas would translate into one thing esteem:

template 
thought rust_iterator = requires (I i) {
    // for some motive in my talks, I caught with the C++
    // naming of 'reference' right here as an different of as an different
    // the expend of the significantly higher name 'item_type'
    typename I::item_type;
    { i.subsequent() } -> same_as>;
};

And certainly one of the important examples that I show is put in force a scheme iterator (what in C++20 we name views::turn out to be), which appears to be like esteem this:

template  F>
struct map_iterator {
    I irascible;
    F func;

    the expend of item_type = invoke_result_t;

    auto subsequent() -> no longer compulsory {
        return irascible.subsequent().scheme(func);
    }
};

It’s possible you’ll presumably per chance per chance presumably look this in action in Tristan Brindle’s float library (the supreme disagreement there, out of doorways of the names of issues, is that his library has particular treatment for limitless ranges). This, to me, is a extraordinarily fine implementation that has a fine form of symmetry: scheme for iterators is conducted by blueprint of scheme for no longer compulsory.

Other than clearly, we don’t obtain no longer compulsory – which is in fact important to acquire in this model. It’s no longer favorable important to shield a long way from copying objects that you just favorable are looking for to iterate over, however semantically it will also very wisely be important that you just focus on with that T as an different of merely some T. So all people knows already that we now must switch our uses of no longer compulsory (every in the thought and in the implementation of map_iterator) to make expend of workaround as an different. But favorable esteem T* didn’t obtain a value_or member feature, it also does no longer esteem a scheme member feature.

And so, again, we are able to also write a workaround for that (the set we again must be cautious about the more than a few kinds of building that we now must obtain):

template 
constexpr auto scheme(P&& ptrish, F f)
{
    the expend of U = decltype(invoke(f, *FWD(ptrish)));
    the expend of Ret = workaround;

    if (ptrish) {
        if constexpr (is_reference_v) {
            return &invoke(f, *FWD(ptrish));
        } else {
            return Ret(invoke(f, *FWD(ptrish)));
        }
    } else {
        return Ret();
    }
}

template  F>
struct map_iterator {
    I irascible;
    F func;

    the expend of item_type = invoke_result_t;

    auto subsequent() -> workaround {
        return scheme(irascible.subsequent(), func);
    }
};

Which will possible be, to me, pretty unpleasant.

At this point I’d deal with to deal with a rapid aside.

§ vector is unpleasant

You’ll customarily hear that the scheme decision of specializing vector was once a unpleasant decision. Certainly, I’ve never met any one that has argued that it was once a legitimate decision. The motive that this is so universally even handed a unpleasant decision is that it way that vector behaves the identical blueprint for all Trather then bool. Which makes this container (basically the most-passe one by a long way) a tiny little bit of jarring.

Now, vector and vector are no longer even that varied. They obtain got most of the identical member capabilities (vector does no longer obtain info(), for occasion), which mostly even obtain the identical issues – there are favorable a few little edge cases the set you some issues favorable don’t work.

For instance:

template 
void f(vector& v) {
    for (auto& elem : v) {
        // expend elem
    }
}

That works for all T rather then bool, because of range_reference_t> is T& for all T (and thus you may presumably per chance per chance presumably also deal with an lvalue reference to it) rather then when T is bool, the set it’s vector::reference, which is a proxy reference kind. Since it’s a prvalue, you may presumably per chance per chance presumably also’t bind a non-const lvalue reference to it. auto const& would obtain worked. auto&& would obtain worked. auto would obtain worked (however obtain a tiny bit varied meaning). Handsome no longer auto&.

Here’s why folk don’t esteem vector. It’s no longer in fact a vector because of it behaves differently. Certainly, favorable this previous week at work, we needed to work round a vector-explicit shriek!

But they’re restful very very identical. They obtain got the identical roughly constructors, they’ve the identical member capabilities, most of whom even obtain the identical semantics. There are many functions of C++ for which there’s wide disagreement in the neighborhood about what’s purely exact, however folk are pretty uniform in the behold that vector may presumably per chance even merely restful no longer obtain been a specialization (and that, seprately, a dynamic_bitset would obtain been worthwhile – and presumably significantly higher at being a dynamic bitset than vector is anyway).

For more, look Howard Hinnant’s On vector.

§ … which makes T* a ideal worse no longer compulsory

If we all agree that vector is unpleasant because of of several subtle differences with vector, then absolutely we are able to also merely restful all agree that T* is a unpleasant no longer compulsory because of it has several very immense and entirely unavoidable differences with no longer compulsory.

Particularly:

  • it is spelled differently from no longer compulsory (trivially: it is spelled T*)
  • it is differently constructible from no longer compulsory (you’ve gotten to jot down &e in one case and e in the different)
  • it has a varied living of supported operations from no longer compulsory

The first of these is what required me to make expend of workaround as an different of merely no longer compulsory, and the 2nd required the grotesque if constexprs in uncover to be in a position to create a workaround in every context that one is being constructed.

The closing of these I touched on a tiny bit, however it absolutely’s value elaborating on. As I famed on the very starting of the post, there are operations in classic between no longer compulsory and T*… however there are even more operations that easiest put together to at least one or the different:

Venn Diagram

Your total operations in the red circle are highly relevant and worthwhile to this shriek. We are looking for to acquire an no longer compulsory reference, so it is worthwhile to acquire the chaining operations that give us a varied roughly no longer compulsory, or to produce a default cost, or to emplace or reset, and even to acquire a throwing accessor.

Your total operations in the orange circle are highly irrelevant to this shriek and may presumably per chance per chance be entirely irascible to make expend of. They’re bugs in a position to happen. We don’t obtain an array, so none of the indexing operations are legitimate. And we don’t obtain an proudly owning pointer, so neither delete nor delete [] are legitimate. However, these operations will in fact bring together – even in the event that they’re all undefined habits.

You’ll deliver that I wrote “pattern matching” in every circles, differently, as an different of hanging them together in the blended living. That’s no longer an oversight. Both P1371 and Herb’s P2392 toughen matching every no longer compulsory and T*, however every papers (despite their many differences) peek these kinds as having varied semantics and match them differently:

  • an no longer compulsory fits in opposition to U or nullopt, because of that’s what it represents exactly.
  • a T* doesn’t match in opposition to a T, rather it fits polymorphically. A Shape* may presumably per chance even match in opposition to a Circle* or a Square*, however no longer in opposition to a Shape.

We don’t obtain pattern matching in C++ but, and we restful received’t in C++23. But at closing we’re going to, and after we obtain, we’ll are looking for to be in a position to look at on whether or no longer our no longer compulsory reference in fact contains a reference, or no longer. We obtain no longer wish to look at whether or no longer we’re… keeping a derived kind or no longer. Here’s but but every other operation that a T* received’t obtain for us.

All in all, there is a grand, grand elevated disagreement between no longer compulsory and T* than there’s between vector and vector. And in every occasion of this disagreement, no longer compulsory is way more right to the shriek of facing an no longer compulsory reference than T* is. T* is merely a extraordinarily sorrowful substitute.

Here’s genuine although you realize for particular you’re facing an no longer compulsory reference, in a non-generic context that doesn’t wish to strive to clutch between T* and no longer compulsory. In case you realize for particular you need an no longer compulsory reference, you are looking for to return the implementation of no longer compulsory reference that supplies basically the most worthwhile operations to the user and the actual individual that supplies the least pitfalls. That is, unequivocally, no longer compulsory.

§ What about optional_ref?

The shriek with T* as an no longer compulsory reference is that it has such varied semantics from no longer compulsory that customarily every expend of it requires a workaround. But what if we as an different wrote a novel kind, dedicated to this shriek: optional_ref. Yell optional_ref obtain been continually an no longer compulsory reference (it has a member T*, and lots others.), that is constructible the identical blueprint as no longer compulsory (from an lvalue of kind T), and has the total identical member capabilities.

Would we restful need no longer compulsory if we had optional_ref?

Yes.

A hypothetical optional_ref is lots closer to fixing the no longer compulsory reference shriek than T* is, however it absolutely restful leaves a few issues to be desired. First, as I pointed out with T*, it is spelled differently. Here’s obvious – its name is optional_ref and no longer no longer compulsory. The kill consequence of the more than a few name is that you just proceed to desire workaround to exist – it’s favorable that as an different of deciding on between no longer compulsory and add_pointer_t, it now chooses between no longer compulsory and optional_ref>. Serene awkward.

Second, this duplicates a form of effort fon the piece of algorithms. no longer compulsory’s scheme and value_or are the identical as optional_ref’s scheme and value_or, however every kinds need every algorithms.

Presumably you don’t care about how grand work the implementer has to acquire for these kinds, however you presumably obtain care about how grand work you’ve gotten to acquire in your end. And that leads into the third shriek precipitated by the actual spelling: how obtain you write an algorithm that takes some roughly no longer compulsory and does one thing with it? If no longer compulsory values and no longer compulsory references obtain been every spelled the identical, you may presumably per chance per chance presumably also write a non-member scheme esteem so:

template 
auto scheme(no longer compulsory, F) -> no longer compulsory>

Successfully, no longer exactly esteem that, because of you presumably don’t are looking for to deal with out a longer compulsory values by cost, however it absolutely’s roughly awkward in C++ to handle this explicit roughly case (look P2481 for some musings). But if we had optional_ref, you may presumably per chance per chance must write two of the total lot your self:

template 
auto scheme(no longer compulsory, F)     -> workaround>

template 
auto scheme(optional_ref, F) -> workaround>

Ticket that the return kinds are the identical for every overloads, because of clearly they’re.

And what did we accomplish from all of this duplication in each situation? It’s unclear to me that we received anything else at all. Obvious, if you’re no longer passe to the premise that no longer compulsory will possible be a reference kind, it can presumably per chance seem superficially treasured to position that info in the name of the kind itself. However it’s a extraordinarily unpleasant tradeoff by blueprint of the total leisure of the utilization. optional_ref is a significantly higher no longer compulsory reference than T*, however it absolutely’s restful a sorrowful substitute.

§ In conclusion

The flexibility to acquire no longer compulsory as a kind, making no longer compulsory a total metafunction, way that in algorithms the set you are looking for to return an no longer compulsory cost of some computed kind U, you may presumably per chance per chance presumably also favorable write no longer compulsory with out having to peril about whether or no longer U occurs to be a reference kind or no longer. This makes such algorithms easy to jot down.

With out no longer compulsory, we both must reject reference kinds in such algorithms (as no longer compulsory::turn out to be for the time being does) or workaround them by returning a T*. But T* has varied building semantics from no longer compulsory (so algorithms constructing this form of thing will deserve to acquire more workarounds) and it has a extraordinarily varied living of provided operations. In explicit, the total operations provided by no longer compulsory however no longer by T* are worthwhile in the context of getting an no longer compulsory reference, whereas the total operations provided by T* however no longer by no longer compulsory are entirely irascible and are merely bugs in a position to happen.

T* appears to be like esteem it customarily is no longer compulsory. Finally, they’ve many properties in classic, and the latter is positively conducted by blueprint of the light. But T* makes for a extraordinarily sorrowful resolution to the shriek of making an try an no longer compulsory reference. Even an optional_ref that solves the shriek of getting the total genuine performance and none of the irascible performance restful can’t pretty offer the total lot that no longer compulsory may presumably per chance even.

no longer compulsory is unequivocally the supreme no longer compulsory reference.

Knowasiak
WRITTEN BY

Knowasiak

Hey! look, i give tutorials to all my users and i help them!