4

Ok so C23 adds function attributes to the language but I'm not sure if anyone actually considered how they are supposed to be used. Consider this example:

[[deprecated]] int foo () { return 0; }
[[nodiscard]]  int bar () { return 0; }

int main()
{
  typedef int foo_t ();
  foo_t* fp1;
  foo_t* fp2;
  foo_t* fp3;

  fp1 = foo;
  fp2 = fp1;
  fp3 = bar;
  fp1();
  fp2();
  fp3();
}

Some things of note here:

  • It is not possible for me to specify an attribute in the typedef because apparently attributes are not attached to the type.
  • The only warning I get in gcc or clang for this entire program is during fp1 = foo; assignment. foo is deprecated.
  • I do not get a warning when assigning fp1 to fp2.
  • I do not get a warning when calling a deprecated function through fp1 or fp2.
  • I do not get a warning when calling fp3 but ignore/discard the result.

Furthermore, there appears to be no check between function declarations and definitions. This compiles cleanly:

[[unsequenced]] int foo ();
[[nodiscard]]   int foo ();
[[deprecated]]  int foo () { return 0; }

Why would I ever want a compiler to allow such nonsense?

Function attributes are not attached to the type, so how can we get "function attribute correctness" and making them work as intended? Or are they just plain underspecified/broken, added to the language at a whim, to be fixed at a later point when someone can be bothered to actually examine the consequences of them?

Am I missing something here?

10
  • 1
    Twelve years too late to complain, since the same can be said of the _Noreturn keyword. Commented Jun 25, 2024 at 10:53
  • @StoryTeller-UnslanderMonica Yeah but that was just an oddball feature for manual optimization purposes. The purpose of the new attributes isn't just optimization, but program safety. Supposedly. Commented Jun 25, 2024 at 11:04
  • 2
    Re “Furthermore, there appears to be no check between function declarations and definitions”: Draft N3096 says “A name or entity declared without the deprecated attribute can later be redeclared with the attribute and vice versa. An entity is considered marked with the attribute after the first declaration that marks it” and there is similar text for some other attributes (I did not check them). So this (a) appears intentional and (b) can be specified by the standard independently for each attribute. One use of it would be that a programmer wishes to be warned… Commented Jun 25, 2024 at 14:16
  • 2
    Re “I do not get a warning when assigning fp1 to fp2”: There is no need for that since a warning was already given for referring to a deprecated function when assigning to fp1. The specification for deprecated is such that no program that refers to a deprecated function will compile without a diagnostic (when the compiler follows recommended practice). The lack of a warning for ignoring a nodiscard result is more troubling, as there bad code can escape warning. Commented Jun 25, 2024 at 14:20
  • 1
    @EricPostpischil That sounds dangerous. So if someone adds an attribute to a function definition with internal linkage, that attribute will just dissolve in thin air when called from a different translation unit. And since the declaration need not match the definition, there's no safety. Commented Jun 26, 2024 at 11:25

1 Answer 1

3

The normal use of deprecated is for a publisher of some software, such as a library, to advise clients that some item is being deprecated. For this purpose, they would mark a declaration of the function in the header they publish, not the definition of the function. When a client translation unit uses an item marked deprecated, the compiler will generate a diagnostic.

This suffices to advise the client of the problem. The fact that further diagnostics are not produced when the value of the item (or effective value, when a function designator is automatically converted to an address) is later used further, having been copied from the item, does not negate the initial diagnostic. This attribute is not intended to modify the type and to change semantics regarding type and its role in assignments and such. Its intent is to provide a diagnostic when a client uses a deprecated item, and it achieves that.

With nodiscard, it is unfortunate that the attribute cannot be propagated to a copy of the value, but shortcoming is compelled by the situation and/or C history; it is not due to faulty design of attributes. Some programs store function addresses in a common array or dynamically assign a common pointer a dynamically selected function address. The array elements or the common pointer have a single type, so we cannot specify that a nodiscard function is a different type from a non-nodiscard function. In some code structures, compiler analysis might be able to trace the value assigned to a pointer and be able to warn that p(); discards the return value when p is known to hold the address of a nodiscard function, but this is of course impossible in the general case. Fully tracking nodiscard would require execution-time support.

In summary, these attributes are not intended to provide new type features. They are intended to provide new warnings that catch some issues, and they are not intended to catch all issues. (This is not new; it is impossible for compilers to catch all errors.) They serve the purposes for which they were designed.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.