C++ Logo

std-proposals

Advanced search

Re: [std-proposals] On the ignorability of restrict (was: Named Return Value Optimisation [[nrvo]])

From: Marcin Jaczewski <marcinjaczewski86_at_[hidden]>
Date: Wed, 11 Feb 2026 00:15:04 +0100
wt., 10 lut 2026 o 20:16 Alejandro Colomar
<une+cxx_std-proposals_at_[hidden]> napisał(a):
>
> Hi Marcin,
>
> On 2026-02-10T16:49:55+0100, Marcin Jaczewski wrote:
> [...]
> > > > > Let's make it more complex:
> > > > >
> > > > > [[nodiscard]] int f(void);
> > > > > int g(void);
> > > > >
> > > > > void h(int (*)(void) );
> > > > > void i(int (*)(void)[[nodiscard]]);
> > > > >
> > > > > int
> > > > > main(void)
> > > > > {
> > > > > h(&f); // This should be diagnosed.
> > > > > i(&f);
> > > > > h(&g);
> > > > > i(&g);
> > > > > }
> > > > >
> > > > > Essentially, [[nodiscard]] should behave like a function qualifier, so
> > > > > that pointers to functions should be able to gain it but not discard it.
> > > > >
> > > > > Otherwise, we have a hole.
> > > >
> > > > But this exactly same case I showed, if you change attribute to argument:
> > > > ```
> > > > void i(int (*arg [[nodiscard]] )(void));
> > > > ```
> > > > This is the property of the argument of the `i` function.
> > >
> > > You had to name the argument, when this is really a property of the
> > > type, and should be expressible in an abstract declarator.
> > >
> > > >
> > > >
> > > > Only problem start when you try pass `i` itself to another function:
> > > >
> > > > ```
> > > > void foo(void (*)(int (*)(void)));
> > > > ```
> > > >
> > > > Here its become probably very painful to handle it but how many times
> > > > you need arbitrary nesting?
> > >
> > > And indeed this shows that the right thing to attribute is the type.
> > > It's not a matter of pain; it's a matter of correctness.
> > >
> >
> > How `[[nodiscard]]` is part of type? it's more usage of value.
>
> qualifiers are also about usage of the value (actually, lvalue; at least
> in C; C++ has to many XXvalue for me to understand).
>

I mean it in a more "general" way, not as defined in C or C++.
Like you have

```
int i = 3 + 2;
```

that is "diffrent" value to

```
int soc = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
```

even if both have the same numerical value. This means you could annotate
every variable that will store this variable. but the type can be kept the same.

> const is about how you can use an lvalue. And it is part of the type.
> [[nodiscard]] is essentially the same: what can do you do with this
> lvalue (the function designator).
>
> > > > Besdied, we can avoid all this by baning both cases like:
> > > >
> > > > ```
> > > > h(&f); // error
> > > > i(&f); //error too
> > > > h([[trust_me_bro]]&f); // ok, explicit "cast"
> > > > i([[trust_me_bro]]&f); // ok, explicit "cast"
> > > > ```
> > >
> > > This is bad; really bad. I don't want the compiler to trust myself.
> > >
> >
> > Do not in 99.99% cases you only call functions like this directly?
> > How many times do you send `malloc` as a callback to other functions?
>
> I can't imagine all the ways people use the language.
> And I can't remember all the ways I've used functions that return
> a resource.
>
> Yes, 99.99% sounds like a reasonable guesstimate.

Besides, when we have resources, should you use RAII?

>
> > And if you do this many times, why not make custom type that itself have this
> > attribute? if you need to guarantee that `int` can't be discarded in some nested
> > callback hell then maybe the problem lies elsewhere? I see more
> > as local helper for corner cases like `empty()` not to build new types.
>
> Casts are always bad. Sometimes, they're necessary, but only because
> the language is not well designed in that corner case. We should try to
> reduce those cases, not add more.

True, but sometimes are needed and trying avoid it could make
everything more complex.

>
> >
> > Beside if you allow attributes aspart of type system then you do not answer me:
> >
> > ```
> > std::is_same_t<int, [[foo]] int>
> > ```
> >
> > is true?
>
> No, it should be false.
>

I think you broke the whole type C++ system.
And end result is same to using `enum class X : int;`

> > Can I overload over attributes? Can I have template
> > specialization based on it?
> > How name mangling will handle it?
>
> I'm a C programmer, and am happy to not need to answer that question.
> Overloading is the largest problem of C++, IMO.
>

But this is C++ mailing list... and if you break this, attributes like
this will be
never added to C++ as fallout will be too big.

> [...]
> > > > > A [[restrict]] replacement of restrict should prioritize diagnostics
> > > > > over optimizations. Thus, it would end up being similar to
> > > > > [[nodiscard]].
> > > > >
> > > > > Tricky question: should the following code result in a diagnostic?
> > > > >
> > > > > alx_at_devuan:~/tmp$ cat restrict.c
> > > > > void f(int *p [[restrict]], int **pp [[restrict]]);
> > > > >
> > > > > int
> > > > > main(void)
> > > > > {
> > > > > int i = 42;
> > > > > int *p = &i;
> > > > >
> > > > > f(p, &p);
> > > > > }
> > > >
> > > > At least my "restric" have explicit arbitrary groups, and would reject
> > > > this code as values are from the same "group".
> > >
> > > Yeah, I wasn't going into that. I kept it simple, for the question.
> > >
> > > > Beside I would here ask programet to be explicit:
> > > >
> > > > ```
> > > > f(p, [[trust_me_restric]]&p); //otherwise diagnostic
> > > > ```
> > >
> > > This is wrong. Programmers should not be trusted for correctness.
> > >
> > > > overall I would use my restic too to handle life time ranges in program like:
> > > > ```
> > > > [[restrict(stack)]]
> > > > [[restrict(temporal)]]
> > > > [[restrict(global)]]
> > > > ```
> > > >
> > > > Of course it will be painful when you use multiple nested `*` but we
> > > > probably should not mix diffrent group too much.
> > > >
> > > > >
> > > > > Similarly tricky question: should the following code result in a
> > > > > diagnostic?
> > > > >
> > > > > alx_at_devuan:~/tmp$ cat restrict.c
> > > > > void f(int *restrict p, int **restrict pp);
> > > > >
> > > > > int
> > > > > main(void)
> > > > > {
> > > > > int i = 42;
> > > > > int *p = &i;
> > > > >
> > > > > f(p, &p);
> > > > > }
> > >
> > > You didn't reply to the tricky questions. Please reply, as they are
> > > quite important regarding the behavior and design of restrict and any
> > > replacements for it.
> > >
> >
> > This is the same code as the previous example. This means my answer
> > is related to this too. If I recall C would say is ok, I would say is not.
>
> Yup, ISO C says this is okay. And indeed, that's the key for the
> ignorability of restrict. If this had to be diagnosed, restrict
> wouldn't be ignorable (at least for some meaning of ignorable).
>

This is wrong, enforcing of attribute is orthogonal to its ignorability:

"""
[Note 5:
The attributes specified in [dcl.attr] have optional semantics: given
a well-formed program, removing all instances of any one of those
attributes results in a program whose set of possible executions
([intro.abstract]) for a given input is a subset of those of the
original program for the same input, absent implementation-defined
guarantees with respect to that attribute.
— end note]
"""

This means `restrict` could enforce this in this case too,
and in this case more its fault of the semantics of this attribute
than of ignorability.

> The strtol(3) function uses restrict in its parameters, and it is fine
> (and even common) to call strtol(p,&p,0).
>
> I also agree with you that this should not be fine, and thus restrict
> should be removed from the specification of strtol(3).
>
> void f(int *p [[restrict]], int **pp [[restrict]]);
> void g(int *p , int **pp );
>
> Should &f and &g be used interchangably as function pointers? Of course
> not, or we'd have the same issues as with [[nodiscard]]. We want
> diagnostics for misuses. [[restrict]] should not be discarded without
> a cast. Then, the ignorability of [[restrict]] is not a thing anymore.
>

This example have more sense than `[[nodiscard]]` case as misuse
will cause UB. And as attribute is attached to parameters is harder to attach it
to variable.

But if we would go your way then you will face unexpected consequences,
if adding attribute to type change it then your first example:
```
         [[nodiscard]] int f(void);
                       int g(void);

         void h(int (*)(void) );
         void i(int (*)(void)[[nodiscard]]);

                 h(&f); // error
                 i(&f);
                 h(&g);
                 i(&g); // error
```

as `f` i `g` are two unrelated function types that can't be interchanged
in any direction. Like in case of `std::is_same_t<int, [[foo]] int> == false`.

>
> Have a lovely night!
> Alex
>
> > But this depends on how you choose to model it.
> >
> > I prefer to look at this as related memory groups, not that these
> > pointers overlap.
> >
> >
> > btw we bit do offtomic as this thread is about nrvo, I could continue this but
> > others would not like too much spam on only tangentially related topics.
> >
> > >
> > > Cheers,
> > > Alex
> > >
> > > --
> > > <https://www.alejandro-colomar.es>
>
> --
> <https://www.alejandro-colomar.es>

Received on 2026-02-10 23:15:19