Pointer overload for ||

250 views
Skip to first unread message

jefferson...@gmail.com

unread,
Mar 11, 2018, 4:38:17 AM3/11/18
to ISO C++ Standard - Future Proposals
Not sure how this will be received -- I haven't posted here much.  Just thought it would be worth mentioning this idea.

Suppose you have an API that you want to optionally allocate storage for your user.  They can either pass in a pointer to some existing allocated space, or pass in a null pointer and space is allocated for them.

void foo(int *x) {
  if (x == nullptr) {
    x = new int;
  }
  // etc.
}

Just figured it would be a decent idea if the || operator received a pointer overload.  Then you could write the more succinct

void foo(int *x) {
  x = x || new int;
}

or even (ducks for cover)

void foo(int *x) {
  x ||= new int;
}

would be nice.

Currently, if I am not mistaken, || (excluding user-defined overloads) is a boolean-only operator.  It is indeed the case that nullptr is the only value that becomes false when cast to a boolean, and all other pointer values become true.  So I don't see how adding an || overload for pointers would break any existing code.

The main thing that would make this a no-go would be if the semantics of || required that both operands be evaluated -- whether or not the left-hand one is false.  Then the whole point as I see it would be lost - the above examples would merely leak memory.

- Jefferson Carpenter

Gašper Ažman

unread,
Mar 11, 2018, 4:59:09 AM3/11/18
to std-pr...@isocpp.org
There is a binary form of the conditional operator that is a gnu extension:

void foo(int *x) {
  x = x?:new int;
}

It's defined as

CEXPR ?: FEXPR;

and rewritten into

{
   auto&& x = CEXPR;
   if (x) { return x; } else { return FEXPR; }
}

Perhaps you could propose that instead, as it's far more general. It's a GNU extension because certain macros (assert comes to mind) are much easier to implement if you have it.

G




--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+unsubscribe@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/1fea07df-46a6-4df2-865a-849be8f8d129%40isocpp.org.

Jefferson Carpenter

unread,
Mar 11, 2018, 5:01:48 AM3/11/18
to std-pr...@isocpp.org

Gašper Ažman

unread,
Mar 11, 2018, 5:03:03 AM3/11/18
to std-pr...@isocpp.org
In my morning fog I forgot I needed to specify that it was the version of assert where you only evaluate the message portion if the condition failed. Not the standard assert.

Nicolas Lesser

unread,
Mar 11, 2018, 5:16:59 AM3/11/18
to std-pr...@isocpp.org
I don't see the point. How is your if not succinct?

if (!ptr)
    ptr = new int;

is very short and understandable. More so than your idea, as that complicates the logical or operator.

Also that inverted logic has a high chance of confusing anyone who comes across it, and it some cases, it is not even clear which or operator you choose.

var = var || allocate();

If allocate doesn't return a pointer, but a boolean (or due to a refactor it changes) then the expression above would no longer do the right thing: assign 1 to var if either expression is true, which is not expected.

You could take inspiration from Python or use the GNU binary op Gašper mentioned:

x = new int if (!x);

But I don't like that either. In short, the first if version is by far the most understandable and clear way of achieving what you want IMO.

Jefferson Carpenter

unread,
Mar 11, 2018, 5:40:12 AM3/11/18
to std-pr...@isocpp.org
On 3/11/2018 4:16 AM, Nicolas Lesser wrote:
> I don't see the point. How is your if not succinct?
>
> if (!ptr)
> ptr = new int;

Yeah, I guess the if statement is good enough. I agree, there's no need
to add the python- or ruby-like form of the if statement.

BTW the only reason I was hesitant to use the ternary operator, "ptr =
ptr ? ptr : new int", is just because you would need to write ptr three
times, yuck.

However, the ternary operator does have the simple advantage over the if
statement that you can use it at the expression level.

> Also that inverted logic has a high chance of confusing anyone who comes
> across it, and it some cases, it is not even clear which or operator you
> choose.
>
> var = var || allocate();
>
> If allocate doesn't return a pointer, but a boolean (or due to a refactor
> it changes) then the expression above would no longer do the right thing:
> assign 1 to var if either expression is true, which is not expected.

I don't know precisely what you mean by "inverted" logic, but I don't
agree that it would necessarily confuse anyone who comes across it - it
would just be unfamiliar syntax, which they would look up. It's true
that the expression would compile regardless of whether allocate()
returns a bool or a pointer, which could cause a surprising bug due to
implicit pointer-to-bool conversion, but I don't feel like that's very
likely, or that it would be very likely that a refactor would cause a
problem there either. Specifically if your allocate() is changed from
returning a pointer to returning a bool during a refactor, its signature
would change in other ways that would cause a compile error there, i.e.
taking a reference to the pointer to allocate as an argument instead of
returning it.

Jefferson Carpenter

Nicolas Lesser

unread,
Mar 11, 2018, 6:00:41 AM3/11/18
to std-pr...@isocpp.org

> Also that inverted logic has a high chance of confusing anyone who comes
> across it, and it some cases, it is not even clear which or operator you
> choose.
>
> var = var || allocate();
>
> If allocate doesn't return a pointer, but a boolean (or due to a refactor
> it changes) then the expression above would no longer do the right thing:
> assign 1 to var if either expression is true, which is not expected.

I don't know precisely what you mean by "inverted" logic

My bad, that's not true. I was thinking of &&.

, but I don't agree that it would necessarily confuse anyone who comes across it - it would just be unfamiliar syntax, which they would look up.  It's true that the expression would compile regardless of whether allocate() returns a bool or a pointer, which could cause a surprising bug due to implicit pointer-to-bool conversion, but I don't feel like that's very likely, or that it would be very likely that a refactor would cause a problem there either.  Specifically if your allocate() is changed from returning a pointer to returning a bool during a refactor, its signature would change in other ways that would cause a compile error there, i.e. taking a reference to the pointer to allocate as an argument instead of returning it.

That is true, my example was pretty bad.


Jefferson Carpenter

--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+unsubscribe@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.

Miguel Ojeda

unread,
Mar 11, 2018, 2:06:59 PM3/11/18
to std-pr...@isocpp.org
On Sun, Mar 11, 2018 at 9:38 AM, <jefferson...@gmail.com> wrote:
> Not sure how this will be received -- I haven't posted here much. Just
> thought it would be worth mentioning this idea.
>
> Suppose you have an API that you want to optionally allocate storage for
> your user. They can either pass in a pointer to some existing allocated
> space, or pass in a null pointer and space is allocated for them.
>
> void foo(int *x) {
> if (x == nullptr) {
> x = new int;
> }
> // etc.
> }
>
> Just figured it would be a decent idea if the || operator received a pointer
> overload. Then you could write the more succinct
>
> void foo(int *x) {
> x = x || new int;
> }
>
> or even (ducks for cover)
>
> void foo(int *x) {
> x ||= new int;
> }

Your first version is the best one, to be honest :-) In any case, if
you really need something like you propose (a pseudo-DSL, generated
code, ...?), then a macro with a descriptive name would be the
simplest and most readable, e.g. for your second version:

#define OR_ELSE(v, e) ((v) ? (v) : (e))

// ...
x = OR_ELSE(x, new int);

For your third version:

#define ENSURE_INIT(v, e) do { if (not (v)) (v) = (e); } while(false)

// ...
ENSURE_INIT(x, new int);

If you are really keen on having the operator, you could always
approximate like:

#define LAZY(x) [](){ return (x); }

template <typename T, typename E>
T operator||(T p, E e)
{
if (p)
return p;
return e();
}

// ...
x = x || LAZY(new int);

Similar for |=; and add something to restrict the overloads as needed.

Now, if you want to make a cool proposal:

template <typename T, expression E>
T operator||(T p, E e)
{
if (p)
return p;
return e;
}

// ...
x = x || new int;

;-)

Cheers,
Miguel

inkwizyt...@gmail.com

unread,
Mar 11, 2018, 8:28:34 PM3/11/18
to ISO C++ Standard - Future Proposals

This solution is not perfect because it reuse `v` multiple times, if value is not simply value it could be problematic.

Better would be using lambda similar like in your next point:


#define OR_ELSE(v, e) ([&](auto MACRO_OR_ELSE_VALUE_X){ return (MACRO_OR_ELSE_VALUE_X ? MACRO_OR_ELSE_VALUE_X : (e)); }(v))



This will allow evaluate `v` only once and can be inserted in other expressions:

auto z = h() ? OR_ELSE(OR_ELSE(x, foo()), bar()) : zz();


 

Richard Hodges

unread,
Mar 12, 2018, 3:33:56 AM3/12/18
to std-pr...@isocpp.org
I am a little surprised and dismayed at the eagerness to resort to macros.

We can already safely overload operators with a custom generator type. No need for macros, language extensions or any other magic.






--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+unsubscribe@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.

Larry Evans

unread,
Mar 12, 2018, 6:33:03 AM3/12/18
to std-pr...@isocpp.org
On 03/11/2018 03:38 AM, jefferson...@gmail.com wrote:
> Not sure how this will be received -- I haven't posted here much. Just
> thought it would be worth mentioning this idea.
>
> Suppose you have an API that you want to optionally allocate storage for
> your user. They can either pass in a pointer to some existing allocated
> space, or pass in a null pointer and space is allocated for them.
>
> void foo(int *x) {
> if (x == nullptr) {
> x = new int;
> }
> // etc.
> }
>
> Just figured it would be a decent idea if the || operator received a
> pointer overload. Then you could write the more succinct
>
> void foo(int *x) {
> x = x || new int;
> }
>
> or even (ducks for cover)
>
> void foo(int *x) {
> x ||= new int;
> }
>
> would be nice.
>
There's precedent in another language, ruby. An example use is:

https://github.com/tardate/pdf-reader-turtletext/blob/master/lib/pdf/reader/positional_text_receiver.rb#L16

with explanation here:

http://www.rubyinside.com/what-rubys-double-pipe-or-equals-really-does-5488.html


Richard Hodges

unread,
Mar 12, 2018, 6:42:27 AM3/12/18
to std-pr...@isocpp.org
On 12 March 2018 at 08:33, Richard Hodges <hodg...@gmail.com> wrote:
I am a little surprised and dismayed at the eagerness to resort to macros.

We can already safely overload operators with a custom generator type. No need for macros, language extensions or any other magic.





However, please don't take my posting "how-to" code as an endorsement.

Having written this, I then implemented a similar concept for unique_ptr (a little more tricky because of the redundant r-value assignment).

This led me to then look at the "|=" case, but I don't think that makes semantic sense when applied to pointers:

Consider:

p |= new_thing();   

In the above code, do we mean that we wish to replace p with a new thing if empty, or does it mean that we wish to merge a new thing into the object referenced by p?


I am inclined to argue that to me it means the latter.

Miguel Ojeda

unread,
Mar 12, 2018, 7:07:05 AM3/12/18
to std-pr...@isocpp.org
On Mon, Mar 12, 2018 at 8:33 AM, Richard Hodges <hodg...@gmail.com> wrote:
> I am a little surprised and dismayed at the eagerness to resort to macros.
>
> We can already safely overload operators with a custom generator type. No
> need for macros, language extensions or any other magic.
>
> live demonstration: http://coliru.stacked-crooked.com/a/bc751df610a220ce

Not sure what is your point. I already suggested the functor approach,
plus the restriction of the overloads as needed if he wanted to use
operator||.

As for the other approaches, I am unsure what do you find "magical"
about them. They are quite clear. :-)

By the way, embedding the code in the email is better for discussion
and archival purposes.

Cheers,
Miguel

Miguel Ojeda

unread,
Mar 12, 2018, 7:18:55 AM3/12/18
to std-pr...@isocpp.org
On Mon, Mar 12, 2018 at 1:28 AM, <inkwizyt...@gmail.com> wrote:
>
>
> On Sunday, March 11, 2018 at 7:06:59 PM UTC+1, Miguel Ojeda wrote:
>>
>> Your first version is the best one, to be honest :-) In any case, if
>> you really need something like you propose (a pseudo-DSL, generated
>> code, ...?), then a macro with a descriptive name would be the
>> simplest and most readable, e.g. for your second version:
>>
>> #define OR_ELSE(v, e) ((v) ? (v) : (e))
>>
>> // ...
>> x = OR_ELSE(x, new int);
>>
>> For your third version:
>>
>> #define ENSURE_INIT(v, e) do { if (not (v)) (v) = (e); } while(false)
>>
>> // ...
>> ENSURE_INIT(x, new int);
>>
>
> This solution is not perfect because it reuse `v` multiple times, if value
> is not simply value it could be problematic.
>
> Better would be using lambda similar like in your next point:
>
>
> #define OR_ELSE(v, e) ([&](auto MACRO_OR_ELSE_VALUE_X){ return
> (MACRO_OR_ELSE_VALUE_X ? MACRO_OR_ELSE_VALUE_X : (e)); }(v))
>
>
>
> This will allow evaluate `v` only once and can be inserted in other
> expressions:
>
> auto z = h() ? OR_ELSE(OR_ELSE(x, foo()), bar()) : zz();
>

Indeed! The usual problems (and workarounds) with macros apply --
which is why it would have been great if C++ would have had something
similar to the "expression E" thingy I put above. :-)

Thanks for pointing it out!

Cheers,
Miguel

Miguel Ojeda

unread,
Mar 12, 2018, 7:42:18 AM3/12/18
to std-pr...@isocpp.org
On Mon, Mar 12, 2018 at 11:42 AM, Richard Hodges <hodg...@gmail.com> wrote:
>
> Consider:
>
> p |= new_thing();
>
> In the above code, do we mean that we wish to replace p with a new thing if
> empty, or does it mean that we wish to merge a new thing into the object
> referenced by p?

For me ||= would be the replace. But since we don't have ||=, I just
picked |=, which indeed looks fishy.

Now, what would be a nice experiment would be ||, &&, ||= and &&= with
short-circuit semantics, which could be implemented if we had the
"expression E" stuff:

template <typename T, expression E>
operator||=(T & t, E)
{
if (t)
return;
t = E;
}

Then:

x ||= new int;

would have the expected results.

Cheers,
Miguel

Gašper Ažman

unread,
Mar 13, 2018, 7:26:46 AM3/13/18
to std-pr...@isocpp.org
But my point was we *already have* an operator with the exact semantics of the macro above - it's just a gnu extension. We could just propose that!


x = x?:new int;

It's unambiguous, it's obvious it's a conditional, it's a one-liner, and more importantly, it can be used for way more than just pointers. Oh, and we have implementations and knowledge of how it behaves, and it's also not a weird new form of || or |.

It also fails to compile if "x" and the expression if x is falsy don't have a common type, which is *also* important.

So, I'd encourage the author of this proposal to instead propose the gnu extension as an actual feature, instead of trying to fit a "don't evaluate this" model as a special case for just certain types - making user-defined types less like built-in types is an explicit no-no these days, and this proposal is just trying to special-case pointers.

G



Cheers,
Miguel

--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+unsubscribe@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.

Jakob Riedle

unread,
Mar 16, 2018, 9:47:44 AM3/16/18
to ISO C++ Standard - Future Proposals


Am Dienstag, 13. März 2018 12:26:46 UTC+1 schrieb Gašper Ažman:
But my point was we *already have* an operator with the exact semantics of the macro above - it's just a gnu extension. We could just propose that!


x = x?:new int;

It's unambiguous, it's obvious it's a conditional, it's a one-liner, and more importantly, it can be used for way more than just pointers. Oh, and we have implementations and knowledge of how it behaves, and it's also not a weird new form of || or |.

It also fails to compile if "x" and the expression if x is falsy don't have a common type, which is *also* important.

+1. I'm all about it! 

T. C.

unread,
Mar 16, 2018, 6:12:40 PM3/16/18
to ISO C++ Standard - Future Proposals
This was proposed in N4120 and rejected by EWG.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposal...@isocpp.org.

To post to this group, send email to std-pr...@isocpp.org.

Miguel Ojeda

unread,
Mar 17, 2018, 12:39:14 AM3/17/18
to std-pr...@isocpp.org
On Tue, Mar 13, 2018 at 12:26 PM, Gašper Ažman <gasper...@gmail.com> wrote:
> But my point was we *already have* an operator with the exact semantics of
> the macro above - it's just a gnu extension. We could just propose that!
>
> https://gcc.gnu.org/onlinedocs/gcc/Conditionals.html
>
> x = x?:new int;
>
> It's unambiguous, it's obvious it's a conditional, it's a one-liner, and

The question is: what do you gain by introducing it? Clarity? I am not
convinced it is simpler than the alternatives. Expressiveness? You can
already do the same in roughly the same amount of code (and also "in
one line" as well).

Moreover, for complex types, this:

if (x) x = y;

can potentially be much better, because the assignment is only done in one case.

> more importantly, it can be used for way more than just pointers. Oh, and we
> have implementations and knowledge of how it behaves, and it's also not a
> weird new form of || or |.

What "new forms"? They are just overloads, and adding new operators is
about as confusing as operator overloading. In both cases, you end up
having to memorize yet another idiom that people will use in some --
but not all -- projects.

In addition, ||, | and |= can currently be used for anything as well,
not just pointers. This is not the same as saying that they *should*,
but it is not an advantage of ?: either.

Cheers,
Miguel

Matthew Woehlke

unread,
Jul 26, 2018, 4:15:13 PM7/26/18
to std-pr...@isocpp.org, jefferson...@gmail.com
On 2018-03-11 04:38, jefferson...@gmail.com wrote:
> Just figured it would be a decent idea if the || operator received a
> pointer overload. Then you could write the more succinct
>
> void foo(int *x) {
> x = x || new int;
> }

Apologies for the thread necro, but I'm surprised no one mentioned:

x or (x = new int); // s/or/||/ if you prefer

--
Matthew
Reply all
Reply to author
Forward
0 new messages