Consolidating GNU C's and C23's deprecated function attribute

78 Views Asked by At

The problem I am facing when using the below macro

// Assume that __GNUC__ or __clang__ is defined. 

#if defined(__has_c_attribute)
    #if __has_c_attribute(deprecated)
        #define ATTRIBUTE_DEPRECATED(...)          [[deprecated(__VA_ARGS__)]]
    #endif    /* deprecated */
#else  
    #define ATTRIBUTE_DEPRECATED(...)              __attribute__((deprecated(__VA_ARGS__)))
#endif    /* __has_c_attribute */

is that __attribute__((deprecated())) and __attribute__((deprecated)) are equivalent, but [[deprecated()]] and [[deprecated]] are not.

The #else block works correctly when called with either of these:

ATTRIB_DEPRECATED()
ATTRIB_DEPRECATED("privat")

because GNU C allows the parameter-list of the attribute to be empty.

But when compiling with std=c2x, the #if block does not, and fails with:

deprecated.c:12:14: error: parentheses must be omitted if attribute argument list is empty

Is there a way I can get the same code to work with C23 as well?

It'd also be nicer if I could write:

ATTRIB_DEPRECATED

instead of:

ATTRIB_DEPRECATED()

if no argument was to be provided.

For a MRE:

// Assume that __GNUC__ or __clang__ is defined. 

#if defined(__has_c_attribute) 
    #if __has_c_attribute(deprecated)
        #define ATTRIBUTE_DEPRECATED(...)          [[deprecated(__VA_ARGS__)]]
    #endif
#else  
    #define ATTRIBUTE_DEPRECATED(...)              __attribute__((deprecated(__VA_ARGS__)))
#endif    /* __has_c_attribute */

ATTRIBUTE_DEPRECATED() int min(int a, int b)
{
    return a < b ? a : b;
}

int main() {
    return min(10, 20);
}

The above code would only compile f __has_c_attribute was not defined.

2

There are 2 best solutions below

0
gulpr On BEST ANSWER

I believe you have to have two separate macros for that


#if defined(__has_c_attribute) 
    #if __has_c_attribute(deprecated)
        #define ATTRIBUTE_DEPRECATED          [[deprecated]]
        #define ATTRIBUTE_DEPRECATEDR(...)    [[deprecated(#__VA_ARGS__)]]
    #endif
#else  
    #define ATTRIBUTE_DEPRECATED              __attribute__((deprecated))
    #define ATTRIBUTE_DEPRECATEDR(...)        __attribute__((deprecated))
#endif    /* __has_c_attribute */



ATTRIBUTE_DEPRECATEDR(this is test) int min(int a, int b)
{
    return a < b ? a : b;
}

ATTRIBUTE_DEPRECATED int max(int a, int b)
{
    return a > b ? a : b;
}

int main() {
    return min(max(10,20), max(10, 20));
}

https://godbolt.org/z/8arY74Gsd

0
KamilCuk On

Just VA_OPT it.

#define ATTRIBUTE_DEPRECATED(...)  [[deprecated __VA_OPT__( (__VA_ARGS__) )]]

ATTRIBUTE_DEPRECATED()
ATTRIBUTE_DEPRECATED("bla")

Below is without VA_OPT. First we take code from https://gustedt.wordpress.com/2010/06/08/detect-empty-macro-arguments/ . Then we add some overload helper to overload based on the return of ISEMPTY(). Each overload, just adds the argument or not.

#define _ARG16(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, ...) _15
#define HAS_COMMA(...) _ARG16(__VA_ARGS__, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0)
#define _TRIGGER_PARENTHESIS_(...) ,
 
#define ISEMPTY(...)                                                    \
_ISEMPTY(                                                               \
          /* test if there is just one argument, eventually an empty    \
             one */                                                     \
          HAS_COMMA(__VA_ARGS__),                                       \
          /* test if _TRIGGER_PARENTHESIS_ together with the argument   \
             adds a comma */                                            \
          HAS_COMMA(_TRIGGER_PARENTHESIS_ __VA_ARGS__),                 \
          /* test if the argument together with a parenthesis           \
             adds a comma */                                            \
          HAS_COMMA(__VA_ARGS__ (/*empty*/)),                           \
          /* test if placing it between _TRIGGER_PARENTHESIS_ and the   \
             parenthesis adds a comma */                                \
          HAS_COMMA(_TRIGGER_PARENTHESIS_ __VA_ARGS__ (/*empty*/))      \
          )

#define PASTE5(_0, _1, _2, _3, _4) _0 ## _1 ## _2 ## _3 ## _4
#define _ISEMPTY(_0, _1, _2, _3) HAS_COMMA(PASTE5(_IS_EMPTY_CASE_, _0, _1, _2, _3))
#define _IS_EMPTY_CASE_0001 ,

// -----------------------------

#define CONCAT(a, b)  a##b
#define XCONCAT(a, b)  CONCAT(a, b)

#define ATTRIBUTE_DEPRECATED1()  \
        [[deprecated]]
#define ATTRIBUTE_DEPRECATED0(...)  \
        [[deprecated(__VA_ARGS__)]]
#define ATTRIBUTE_DEPRECATED(...)  \
        XCONCAT(ATTRIBUTE_DEPRECATED, ISEMPTY(__VA_ARGS__))(__VA_ARGS__)


ATTRIBUTE_DEPRECATED()
ATTRIBUTE_DEPRECATED("bla")

Note passing an empty () list for macro with (...) is technically non-conforming and breaking https://port70.net/~nsz/c/c11/n1570.html#6.10.3p4 . But no one cares.