Date: Wed, 11 Feb 2026 00:35:08 +0100
Hi Marcin,
On 2026-02-11T00:15:04+0100, Marcin Jaczewski wrote:
[...]
> > > > 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.
I think you're conflating [[nodiscard]] with
[[gnu::malloc(deallocator)]], which I think should be allowed also on
integer return values.
See <https://gcc.gnu.org/bugzilla/show_bug.cgi?id=106854>.
>
> > 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?
I prefer avoiding that, as you can guess from a C programmer. :)
I prefer explicit handling of resources. [[gnu::malloc()]] helps a lot
with handling resources, and results in much more readable code than
all-implicit in C++, IMO.
>
> >
> > > 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.
I don't think it would be so complex. GCC's __attribute__((const)) is
part of the type (at least in C, I'd guess also in C++?). It would be
interesting to know how well that behaves.
> > > 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`.
I agree [[restrict]] should not be part of the type of the parameter.
That would be wrong, as you said. It should be part of the function
type, instead. As a consequence, a better syntax would be more like
void f(int *p, int **pp) [[restrict(1,2)]];
Cheers,
Alex
On 2026-02-11T00:15:04+0100, Marcin Jaczewski wrote:
[...]
> > > > 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.
I think you're conflating [[nodiscard]] with
[[gnu::malloc(deallocator)]], which I think should be allowed also on
integer return values.
See <https://gcc.gnu.org/bugzilla/show_bug.cgi?id=106854>.
>
> > 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?
I prefer avoiding that, as you can guess from a C programmer. :)
I prefer explicit handling of resources. [[gnu::malloc()]] helps a lot
with handling resources, and results in much more readable code than
all-implicit in C++, IMO.
>
> >
> > > 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.
I don't think it would be so complex. GCC's __attribute__((const)) is
part of the type (at least in C, I'd guess also in C++?). It would be
interesting to know how well that behaves.
> > > 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`.
I agree [[restrict]] should not be part of the type of the parameter.
That would be wrong, as you said. It should be part of the function
type, instead. As a consequence, a better syntax would be more like
void f(int *p, int **pp) [[restrict(1,2)]];
Cheers,
Alex
-- <https://www.alejandro-colomar.es>
Received on 2026-02-10 23:35:20
