1. Introduction
Since C++14, the standard library has provided transparent function objects (functors with template specialization and member type) for most C++ operators, introduced by [N3421]. However, the shift operators ( and ) were not included.
1.1. Historical Context from N3421
The original [N3421] proposal noted that shift operators "could be useful" but deferred them as "slightly beyond completely trivial to specify." The author raised questions about design details for operators like address-of (should it use or ?), which warranted deferral. However, shift operators have no such ambiguity—they simply forward to and following the same transparent pattern as , , etc.
1.2. Purpose of This Paper
This paper proposes adding and to complete the set of bitwise operator function objects in . The design is straightforward—these functors follow the same transparent pattern as existing operators like and , simply forwarding to the built-in and operators.
2. Motivation
2.1. Current State
The standard library currently provides transparent function objects for:
-
Bitwise operations:
,bit_and <> ,bit_or <> ,bit_xor <> bit_not <> -
Arithmetic operations:
,plus <> ,minus <> ,multiplies <> ,divides <> ,modulus <> negate <> -
Comparison operations:
,equal_to <> ,not_equal_to <> ,greater <> ,less <> ,greater_equal <> less_equal <> -
Logical operations:
,logical_and <> ,logical_or <> logical_not <>
Notably absent are transparent function objects (operator wrappers) for the bitwise shift operators and .
2.2. Relationship to P3793R1
This proposal is complementary to [P3793R1], which proposes and as direct function calls in (similar to /).
-
P3793R1 provides named functions in
that define behavior for shift operations that would otherwise be undefined (e.g., shifting by negative amounts or amounts >= width)< bit > -
P4006 (this paper) provides transparent function objects in
(like< functional > ,bit_and <> ) that forward directly to the built-inbit_or <> and<< operators, preserving all their semantics including undefined behavior>>
The facilities serve different purposes:
-
provides defined behavior even for edge casesstd :: shl ( x , n ) -
is a transparent wrapper forstd :: bit_lshift <> {}( x , n ) with identical semantics tox << n .<<
2.3. Use Cases
2.3.1. Completing the Bitwise Operator Family
The standard library provides transparent function objects for all other bitwise operators (, , , ). Omitting shift operators creates an inconsistency—users must write verbose lambdas for shifts while using concise functors for other bitwise operations. This proposal completes the family for consistency and uniformity.
2.3.2. Algorithm Composition
Transparent function objects enable heterogeneous comparisons and operations in algorithms:
// Without P4006 - verbose lambda std :: transform ( values . begin (), values . end (), shifts . begin (), results . begin (), []( auto v , auto s ) { return v << s ; }); // With P4006 - concise and self-documenting std :: transform ( values . begin (), values . end (), shifts . begin (), results . begin (), std :: bit_lshift <> {});
2.3.3. Generic Programming
Transparent function objects exist to enable uniform passing of operators to generic algorithms and functions. Without and , there is a gap in this uniformity since shift operators cannot be passed the same way as other binary operators like or .
Example:
// Library code that needs to work with any binary operator template < typename BinOp > auto apply_operation ( auto lhs , auto rhs , BinOp op ) { return op ( lhs , rhs ); } // Without proposal: can’t pass shift operators uniformly apply_operation ( x , y , std :: plus <> {}); // ✓ apply_operation ( x , y , std :: bit_and <> {}); // ✓ apply_operation ( x , y , []( auto a , auto b ) { return a << b ; }); // ✗ verbose // With proposal: apply_operation ( x , y , std :: bit_lshift <> {}); // ✓
2.3.4. Supporting Generic Library Customization
Generic libraries benefit from uniform operator discovery through transparent function objects. For example, work on support for User-Defined Types (UDTs) in [P2964R1] explores customization points for optimizing operations on specific element types. While this proposal is independent of P2964R1’s acceptance, it enables such libraries to handle all operators uniformly when providing customization points.
For operations like addition, implementations can check that is valid for the element type. Without and , shift operators lack this uniform discovery mechanism, creating an asymmetry in customization point design.
3. Design Rationale
3.1. Other Operators Not Proposed
This proposal focuses solely on shift operators because they are the only operators that:
-
Follow the same pure-function pattern as existing transparent functors
-
Complete the bitwise operator family (
,bit_and ,bit_or ,bit_xor ➜bit_not ,bit_lshift )bit_rshift -
Have no semantic ambiguities
Other operators remain unsuitable for reasons detailed in § 10 Appendix: Operator Coverage Summary.
3.2. Operator Overload Context
While and are commonly overloaded for stream I/O in C++, the prefix clearly indicates these functors wrap the bitwise shift operations, not stream operations. This naming convention follows the existing pattern where wraps (not ) and wraps .
These functors work with any type that provides or , including:
-
Built-in integral types performing bitwise shifts
-
User-defined types with overloaded shift operators (whether for bitwise shifts or other purposes)
The functors are completely transparent—they forward to whatever / means for the operand types.
4. Implementation Experience
This proposal follows the exact same pattern as existing transparent function objects in . The implementation is trivial (as N3421 predicted):
template <> struct bit_lshift < void > { template < class T , class U > constexpr auto operator ()( T && lhs , U && rhs ) const noexcept ( noexcept ( std :: forward < T > ( lhs ) << std :: forward < U > ( rhs ))) -> decltype ( std :: forward < T > ( lhs ) << std :: forward < U > ( rhs )) { return std :: forward < T > ( lhs ) << std :: forward < U > ( rhs ); } using is_transparent = void ; };
The author has prototyped this implementation and tested it with the use cases in § 2.3 Use Cases, confirming it works as expected with no surprises.
5. Impact on Existing Code
None. This is a pure library addition with no changes to existing facilities. Code using lambdas for shift operations continues to work unchanged.
6. Teachability
Users already understand transparent function objects. Teaching materials simply add:
-
"Use
to pass the left-shift operator to algorithms"std :: bit_lshift <> {} -
"Use
to pass the right-shift operator to algorithms"std :: bit_rshift <> {}
This follows the same pattern as teaching , , etc. The prefix makes the purpose immediately clear.
7. Proposed Design
Add two new transparent function objects to :
template < class T = void > struct bit_lshift { constexpr T operator ()( const T & lhs , const T & rhs ) const { return lhs << rhs ; } }; template < class T = void > struct bit_rshift { constexpr T operator ()( const T & lhs , const T & rhs ) const { return lhs >> rhs ; } }; // Transparent specializations template <> struct bit_lshift < void > { template < class T , class U > constexpr auto operator ()( T && lhs , U && rhs ) const noexcept ( noexcept ( std :: forward < T > ( lhs ) << std :: forward < U > ( rhs ))) -> decltype ( std :: forward < T > ( lhs ) << std :: forward < U > ( rhs )) { return std :: forward < T > ( lhs ) << std :: forward < U > ( rhs ); } using is_transparent = void ; }; template <> struct bit_rshift < void > { template < class T , class U > constexpr auto operator ()( T && lhs , U && rhs ) const noexcept ( noexcept ( std :: forward < T > ( lhs ) >> std :: forward < U > ( rhs ))) -> decltype ( std :: forward < T > ( lhs ) >> std :: forward < U > ( rhs )) { return std :: forward < T > ( lhs ) >> std :: forward < U > ( rhs ); } using is_transparent = void ; };
8. Design Alternatives
8.1. Naming
8.1.1. Chosen Names: bit_lshift and bit_rshift
This proposal uses and for the following reasons:
-
Consistency with existing bitwise operators: The standard library already uses the
prefix for bitwise operation functors:bit_ ,bit_and <> ,bit_or <> andbit_xor <> . Usingbit_not <> andbit_lshift <> completes this family.bit_rshift <> -
Avoids naming conflict: C++20 added
andstd :: shift_left as algorithms instd :: shift_right ([alg.shift]) that shift ranges of elements left/right. Using< algorithm > /bit_lshift avoids this conflict.bit_rshift -
Clear semantic distinction from P3793R1: [P3793R1] proposes
andstd :: shl () functions instd :: shr () that provide defined behavior for shift operations that would otherwise be undefined. In contrast,< bit > andbit_lshift <> are transparent wrappers that forward directly to the built-inbit_rshift <> and<< operators, preserving all their semantics including undefined behavior.>>
8.1.2. Alternative names
Using and was considered for brevity and potential alignment with [P3793R1]. However:
-
Semantic mismatch: The P3793R1 functions define behavior for cases where
/<< have undefined behavior. These functors simply forward to>> /<< without any special handling.>> -
Inconsistency: Would break the
naming pattern for bitwise operations.bit_ *
The names and would be more descriptive, but these names are already used for range-shifting algorithms in since C++20.
Using and without the prefix was considered but the prefix immediately identifies these as bitwise operations, distinguishing them from other potential shift operations.
9. Wording
Add to [version.syn]:
#define __cpp_lib_bitwise_shift_functors YYYYMML // also in <functional>
Add to [functional.syn] in :
namespace std { // ... // [bitwise.operations], bitwise operations template < class T = void > struct bit_and ; template < class T = void > struct bit_or ; template < class T = void > struct bit_xor ; template < class T = void > struct bit_not ; template < class T = void > struct bit_lshift ; template < class T = void > struct bit_rshift ; // ... }
Add new subsection [bitwise.operations.shift] after [bitwise.operations]:
template < class T = void > struct bit_lshift { constexpr T operator ()( const T & lhs , const T & rhs ) const ; }; Effects: Equivalent to:
return lhs << rhs ; template < class T = void > struct bit_rshift { constexpr T operator ()( const T & lhs , const T & rhs ) const ; }; Effects: Equivalent to:
return lhs >> rhs ; template <> struct bit_lshift < void > { template < class T , class U > constexpr auto operator ()( T && lhs , U && rhs ) const noexcept ( noexcept ( std :: forward < T > ( lhs ) << std :: forward < U > ( rhs ))) -> decltype ( std :: forward < T > ( lhs ) << std :: forward < U > ( rhs )); using is_transparent = unspecified ; }; Effects: Equivalent to:
return std :: forward < T > ( lhs ) << std :: forward < U > ( rhs ); template <> struct bit_rshift < void > { template < class T , class U > constexpr auto operator ()( T && lhs , U && rhs ) const noexcept ( noexcept ( std :: forward < T > ( lhs ) >> std :: forward < U > ( rhs ))) -> decltype ( std :: forward < T > ( lhs ) >> std :: forward < U > ( rhs )); using is_transparent = unspecified ; }; Effects: Equivalent to:
return std :: forward < T > ( lhs ) >> std :: forward < U > ( rhs ); [Note 1:
andbit_lshift forward directly to the built-in operators and preserve all their semantics, including undefined behavior when the shift amount is negative or exceeds the width of the promoted left operand. —end note]bit_rshift [Note 2: While
andoperator << are commonly overloaded for stream I/O, theoperator >> prefix clearly indicates these functors wrap the bitwise shift operations, not stream operations. The functors work with any types that provide these operators. —end note]bit_
10. Appendix: Operator Coverage Summary
This appendix documents which C++ operators have transparent function objects and why others are not proposed.
| Category | Operators | Status |
|---|---|---|
| Arithmetic | , , , , , unary
| ✓ Covered (, , , , , )
|
| Bitwise (non-shift) | , , ,
| ✓ Covered (, , , )
|
| Bitwise (shift) | ,
| ⊕ This proposal (, )
|
| Comparison | , , , , ,
| ✓ Covered (, , etc.)
|
| Logical | , ,
| ✓ Covered (, , )
|
| Assignment | , , , , etc.
| ✗ Mutating, incompatible with perfect forwarding |
| Increment/Decrement | ,
| ✗ Mutating, prefix/postfix ambiguity |
| Member access | , , ,
| ✗ Context-dependent semantics |
| Other | (address), (deref), , , , , unary
| ✗ Various design issues (see N3421) |
Why shift operators are different: Unlike excluded operators, shifts are pure functions with no side effects, no design ambiguities (unlike address-of’s vs question), and complete the bitwise operator family.