Answer to the Exact Question
To make std::format reliably usable for screen‑reader‑friendly output, the C++ standard should impose the following constraints:
- Logical‑Order Output: The formatted string must represent the logical sequence of grapheme clusters, independent of any bidirectional or shaping rules that the terminal or UI layer applies.
- Explicit Normalization: Implementations should provide an optional normalization flag (e.g.,
format::normalization::nfc) so that callers can guarantee a canonical form that assistive technologies expect.
- Separation of Presentation and Accessibility Facets: Locale‑dependent decorations (digit grouping, currency symbols, ordinal suffixes) should be emitted into a distinct “presentation” facet that can be stripped or replaced when the string is used as an accessible name or description.
- Bidirectional‑Safe API: The format library should expose a “bidi‑isolation” option that inserts U+2066 (LRI) and U+2069 (RLI) around the formatted substring, ensuring that downstream readers interpret the logical order correctly.
- Consistent Width Estimation for Non‑ASCII Text: Width calculations for alignment and padding should be defined in terms of grapheme cluster width, not code‑point count, and must be stable across standard library implementations.
With these constraints, screen readers will receive a predictable logical string, while the UI can still apply locale‑specific visual styling.
Confirmed Facts from the Standard
std::format supports locale‑aware numeric formatting via the "L" format specifier and overloads that accept std::locale.
- Locale facets such as
std::numpunct provide digit grouping, decimal separators, and currency symbols, but the standard does not define how these should be announced by assistive technology.
- The library operates on sequences of
char or char8_t code units; it does not guarantee grapheme‑cluster or bidirectional ordering.
Likely Explanation and Practical Guidance
Because screen readers consume logical text, the safest path is to let std::format emit a plain, normalized string and let the UI layer add any locale‑specific visual embellishments. If the application must embed grouping or currency symbols in the accessible name, those should be added via the "L" specifier but the caller should also provide a parallel “plain” string without those decorations for assistive use.
Steps for a Typical Accessibility‑Aware Workflow
- Format the core data logically:
auto plain = std::format("{:.2f}", value); // no locale
- Apply locale decorations for visual output only:
auto visual = std::format(std::locale("de_DE"), "{L:.2f}", value);
- Wrap the logical string in bidi isolation if it contains RTL runs:
auto isolated = std::format("{LRI}{plain}{RLI}");
- Provide both strings to the UI: use
visual for labels and plain (or isolated) for accessible names/ARIA labels.
- Verify with the target screen reader to ensure the spoken order matches the logical string.
What Diagnostic Detail Might Alter the Recommendation?
If the application targets a platform where the UI framework automatically normalizes or strips Unicode combining marks (e.g., a web browser with specific CSS rules), the recommendation to use an explicit normalization flag may change. Please confirm the rendering engine or assistive technology in use if that is a concern.
Would a New Facet Be Needed?
Adding a dedicated std::format::accessibility facet that exposes logical order, normalization, and bidi isolation would make the contract explicit. However, the current set of options (locale, "L", and a proposed normalization flag) is sufficient for most use cases, provided the UI layer handles the actual presentation.