Document: P4006R0
Author: Daniel Towner (Intel)
Date: 2026-02-03
Audience: LEWG
Remember when N3421 gave us transparent functors for C++14 but specifically deferred << and >> as "slightly beyond completely trivial to specify"? Twelve years later, someone finally wrote the paper to fill the gap.
P4006R0 proposes std::bit_lshift<> and std::bit_rshift<> - transparent function objects that wrap the bitwise shift operators, completing the bit_and/bit_or/bit_xor/bit_not family. The implementation is exactly what you'd expect: forward to operator<</operator>> with perfect forwarding. The paper also documents why other missing operators (assignment, increment, member access) should stay missing.
We waited twelve years for someone to write a paper that adds two struct templates to
<functional>. The absolute state of standardization throughput.To be fair, LEWG had to get through executors, ranges, and
std::formatfirst. Shift functors were patiently waiting their turn in the queue like the rest of us.I actually read the paper. It's well-scoped and clean, but the interesting question is how this interacts with P3793R1 which proposes
std::shlandstd::shrin<bit>with defined behavior for edge cases.The paper acknowledges this directly:
So if both land, you'd have
std::shl(x, n)for safe shifts andstd::bit_lshift<>{}(x, n)for the raw operator. The question is: in what scenario do you specifically want the UB-preserving wrapper passed to an algorithm instead of a lambda or the safe version? The answer is "when you need uniform functor dispatch and you know your inputs are valid" but that's a pretty narrow lane.Honest question - has anyone ever been actually blocked by the lack of this?
[](auto a, auto b) { return a << b; }is like twenty characters and communicates exactly what it does.The paper's strongest argument isn't convenience. Section 2.3.4 talks about
std::simdneeding uniform operator discovery through transparent functors - a library can check thatstd::bit_and<>is valid for a type but can't do the same for shifts without rolling your own trait. Niche, but it's a real gap in the generic programming story.Named
bit_lshiftbecause they had to disambiguate from stream insertion. Twelve years and the best part of the paper is still the naming section explaining whyoperator<<means three different things in C++.I'm just impressed someone wrote a full paper with proposed wording, an alternatives analysis, and a ten-row appendix covering every C++ operator, for what amounts to twenty lines of code. Thorough.