3 ms·
> N3020 - Qualifier-preserving Standard Functions I just read the actual proposal on it. They describe an example implementation using macros and _Generic. Wh
by EdSchouten 4y ago
> N3020 - Qualifier-preserving Standard Functions
I just read the actual proposal on it. They describe an example implementation using macros and _Generic.
What’s really bad about this is that the arguments provided to these methods are used more than once in the macro body. This will cause:
- gibberish compiler errors in case of typos in the arguments,
- worse compilation times, as the compiler has to process more code.
Constructs like these may lead to an exponential explosion in preprocessed source code size. I hope that nobody implements it that way.
- jonathrg 4y agoHow exactly can this lead to exponential explosion?
- pdw 4y agoCode that does things like strcat(strcat(strcat(strcpy(...), ...), ...), ...)
- jonathrg 4y agoIf I understand the implementation section of the proposal correctly, the preprocessor will just add ome cast to (const char*) per function call (assuming the innermost argument is const). Nothing exponential
- lifthrasiir 4y agoFor the ease of discussion here is the relevant piece of code: #define IS_POINTER_CONST(P) _Generic(1 ? (P) : (void *)(P) \ , void const *: 1 \ , default : 0) #define STATIC_IF(P, T, E) _Generic (&(char [!!(P) + 1]) {0} \ , char (*) [2] : T \ , char (*) [1] : E) #define _STRING_SEARCH_QP(T, F, S, ...) \ STATIC_IF (IS_POINTER_CONST ((S)) \ , (T const *) (F) ((S), __VA_ARGS__) \ , (T *) (F) ((S), __VA_ARGS__)) #define memchr(S, C, N) _STRING_SEARCH_QP(void, memchr, (S), (C), (N)) #define strchr(S, C) _STRING_SEARCH_QP(char, strchr, (S), (C)) #define strpbrk(S1, S2) _STRING_SEARCH_QP(char, strpbrk, (S1), (S2)) #define strrchr(S, C) _STRING_SEARCH_QP(char, strrchr, (S), (C)) #define strstr(S1, S2) _STRING_SEARCH_QP(char, strstr, (S1), (S2)) Here `IS_POINTER_COST` expands `P` twice so `_STRING_SEARCH_QP` expands `T`, `F`, `S` and each variadic argument 2, 2, 4 and 2 times respectively. Note that only `S` and variadic arguments are expressions and evaluated only once, but the number of expanded tokens can blow up exponentially.
- jstimpfle 4y agoI agree. While I didn't make an effort to actually understand the approach here, N3020 was the one that I disliked because it solves too small of an issue in practice and there probably can't be a simple enough solution. Any solution is probably complicated because the issue is only a symptom really. The real issue, in my eyes is, that "const" simply doesn't transfer well across function call boundaries. "const" is always only a local judgement - data that is const for one function is not necessarily const for another function. I've even read an old conversation where Dennis Ritchie was uttering concerns about const when it was initially designed. Typically I use the const keyword only for global static data, because there it makes a real technical difference. Sometimes I declare function pointer parameters as pointer-to-const, partly for reasons of documentation, but I'm always aware of the strstr() issue.
- colonwqbang 4y agoI've encountered this problem in real code bases when you have a getter function for a complex data structure. You have to decide between: field *my_struct_get_field(my_struct *s) const field *my_struct_get_field(const my_struct *s) In practice you will choose the first instance, so you simply won't use const where you could. Which is unfortunate. It would be nice to have a shorthand for this. The nested macro shown in the paper looks like it can fix the problem, but is quite complicated. I would like something like: autoconst field *my_struct_get_field(autoconst my_struct *s) The returned pointer inherits the qualifiers of the argument. Inside the getter function the pointer is const, and there is only one implementation of the function.
- jsmith45 4y agoThe compiler does not need to implement it with the macros shown. They could use other techniques, so long as it has the same net result of preserving constness of the argument. For example, the macro definitions could easily use a macro that resolves to a magic generic intrinsic (which becomes a call the existing function implementation) that maintains the constness of the argument. An exponential code size increase even with the example implementation would only happen if people are nesting these functions inside each other. And while that could happen, it would be unusual to nest them very deeply. So for compilers that just want an easy implementation, and don't really care about quality of error messages or maximizing compile speed, the shown approach is probably fine.
- JonChesterfield 4y ago_Generic is an awful feature relative to attribute((overloadable)) anyway so that's probably a non-issue