C++ Logo

std-proposals

Advanced search

Re: [std-proposals] Named Return Value Optimisation [[nrvo]]

From: Alejandro Colomar <une+cxx_std-proposals_at_[hidden]>
Date: Tue, 10 Feb 2026 15:09:27 +0100
Hi Marcin,

On 2026-02-10T14:44:03+0100, Marcin Jaczewski wrote:
> > > > > > alx_at_devuan:~/tmp$ cat nodiscard.c++
> > > > > > [[nodiscard]] int f(void);
> > > > > >
> > > > > > int
> > > > > > main(void)
> > > > > > {
> > > > > > typeof(int (void) ) *fp1;
> > > > > > typeof(int (void)[[nodiscard]]) *fp2;
> > > > > >
> > > > > > fp1 = &f; // I would expect a diagnostic here.
> > > > > > fp1();
> > > > > >
> > > > > > fp2 = &f;
> > > > > > fp2();
> > > > > > }
> > > > > > alx_at_devuan:~/tmp$ g++ -S -Wall -Wextra nodiscard.c++
> > > > > > nodiscard.c++: In function ‘int main()’:
> > > > > > nodiscard.c++:7:38: warning: ‘nodiscard’ attribute can only be applied to functions or to class or enumeration types [-Wattributes]
> > > > > > 7 | typeof(int (void)[[nodiscard]]) *fp2;
> > > > > > | ^
> >
> > [...]
> > > > No, that was not the point. The point is that the conversion in
> > > > fp1 = &f;
> > > > discards an attribute from the function type, and thus results in calls
> > > > through that function pointer to not have a diagnostic when the return
> > > > value is discarded. This is a hole in the rules.
> > > >
> > > > And the diagnostic you see in fp2 is in fact another hint that the
> > > > design of [[nodiscard]] was bogus. That code should be accepted, and it
> > > > should be the only way of storing a function pointer that holds &f. If
> > > > that's not accepted, there is no way of using [[nodiscard]] safely.
> > > >
> > >
> > > But what does this have to do with ignorability of attributes?
> > > It's possible that you could not add the "not-ignorable" attribute there anyway.
> > > ```
> > > std::is_same<int, [[foo]] int>
> > > ```
> > > how C++ should handle it? even in case where it can't ignore this attribute.
> > >
> > > Only solution I see is that `fp1` itself should have `[[nodiscard]]`,
> >
> > Yes; it should be like fp2, and the attribute should be part of the type
> > (and thus we should have no diagnostic in line 7).
> >
>
> I mean something diffrent, I word this wrongly.
> I means that variable itself should have attribute like:
>
> ```
> typeof(int (void)) *fp2 [[nodiscard]];
> ```
>
> And this would fit the definition of `f` as its attribute is linked to
> name not to its type.
> And this will be fine in the current paradigm.

That would be the wrong choice. The attribute should be part of the
type, to make sure type conversions are okay.

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.

> > > and this could be still ignorable.
> >
> > In part, this is a problem because either
> >
> > a) ignorability of attributes is the problem
> > or
> > b) [[nodiscard]] should not be ignorable, and should be replaced by a
> > non-ignorable attribute.
> >
> > And if b) is chosen, then few very ignorable attributes would remain;
> > so that ignorability of attributes would render attributes DoA. *The*
> > truly ignorable attribute is [[maybe_unused]].
> >
> I can imagine many others that could work fine and be enforced by the compiler
> but safely ignored if the compiler does not bother implementing them.
>
> ```
> int* foo(int* b [[restrict(A,B)]], int* c [[restrict(C)]])
> [[restrict(A,B,C)]]
> {
> *b += 2;
> *c += 2;
> return *b < *c ? b : c;
> }
> ```
>
> Now if the compiler regonzie this attribute then he could only do 2
> loads as pointers should not overlap.
> If not, then it only generates less optimal code. if the programmer
> lied then we get UB, same as `[[noreturn]]`.

restrict is terrible, and your suggested [[restrict]] is similarly
terrible.

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);
 }

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);
 }


Cheers,
Alex

-- 
<https://www.alejandro-colomar.es>

Received on 2026-02-10 14:09:32