[clang] parameter attributes ([[clang::noescape]], __restrict on references) are silently dropped from function(-pointer) types built through templates — no generic-programming path to attributed thunk-pointer types
- Dominant language
- LLVM
- Stars
- 40.5k
- Forks
- 18.7k
- PR merge metrics
- PR metrics pending
Description
### Summary
There is no way to construct a function-pointer type carrying parameter-level attributes (`[[clang::noescape]]`, and `__restrict` on a reference parameter) through a template — both are **silently dropped** when the signature is passed as a template argument, with no diagnostic. Since these attributes are **caller-side** facts (they must be on the function-pointer *type* to affect the indirect callsite), this makes it impossible to write a generic "attributed fn-ptr factory" for type-erasure thunk tables; every dispatch table must hand-spell the full attributed pointer type (or resort to a token-pasting macro).
### Repro (clang 22.1.3, `-S -emit-llvm`, x86_64)
```c++
struct Header { void * payload; int const * coord; };
// direct spelling — attributes written on the fn-ptr parameter
using DirectFn = int const * (*)( [[clang::noescape]] Header & __restrict ) noexcept;
// generic spelling — signature passed through a template
template struct fn_ptr;
template
struct fn_ptr { using type = Ret (*)( Args... ) noexcept; };
using FactoryFn = fn_ptr::type;
int callDirect ( DirectFn f, Header & h ) { return *f( h ); }
int callFactory( FactoryFn f, Header & h ) { return *f( h ); }
```
Emitted IR:
```llvm
; callDirect's indirect call — captures(none) present, restrict in the mangled type (AEIAU…)
%7 = call noundef ptr %5(ptr noundef nonnull align 8 captures(none) dereferenceable(16) %6)
; callFactory's indirect call — BOTH silently dropped (mangling shows plain AEAU…)
%7 = call noundef ptr %5(ptr noundef nonnull align 8 dereferenceable(16) %6)
```
Note the callee *definitions* keeping the attributes does not help: at an indirect call through the erased pointer, only the pointer type's attributes reach the callsite.
### Why this matters
Function-pointer-based type erasure (thunk tables instead of vtables) is a common size/perf technique; the attribute set (`noescape`, `__restrict`, calling convention) is boilerplate one wants to stamp once via a metafunction like `erased_fn_t`. Today:
- `__attribute__((sysv_abi))` etc. CAN be applied at the alias level (works),
- parameter-level attributes CANNOT ride through the template argument, and are dropped **without any `-Wignored-attributes` style diagnostic**, so the pessimization is invisible.
### Ask
1. At minimum: diagnose the silent drop.
2. Ideally: a generic-programming path — either make declaration-level parameter attributes on function *types* participate in template argument deduction/substitution, or provide compiler builtins to conditionally apply `noescape`/`restrict` to reference/pointer parameters in generic contexts (analogous to how `__restrict` already participates in the type system for member functions and mangling when spelled directly).
Contributor guide
Research direction
Start by compiling the provided reproducer with clang 22.1.3 using -S -emit-llvm and compare the indirect calls for DirectFn and FactoryFn. Investigate how parameter-level attributes are represented when the function signature becomes a template argument. Done means either diagnosing the silent drop or establishing a generic path that preserves these attributes at the indirect callsite.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp
- Domain
- compilers
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 32/100