The Standard Library class template std::bitset<N>
has a constructor (C++11 and onwards, unsigned long
argument before C++11)
constexpr bitset(unsigned long long) noexcept
Contrary to many best-practice guidelines, this single-argument constructor is not marked as explicit
. What is the rationale behind this?
The main objection against an explicit
constructor is that copy-initialization from unsigned integers no longer works
constexpr auto N = 64;
std::bitset<N> b(0xDEADC0DE); // OK, direct initialization
std::bitset<N> b = 0xDEADC0DE; // ERROR, copy initialization cannot use explicit constructors
Since std::bitset<N>
is meant as a generalization of unsigned int
, the constructor was probably made implicit to facilitate adapting existing C-style bit-twiddling code based on raw unsigned int
. Making the constructor explicit
would have broken much existing code (and adding it now will equally break much existing code).
UPDATE: doing some Standard archeology, I found N0624 from January 1995 that proposed to add the then brand-new keyword explicit
to all single-argument constructors in the pre-Standard Library draft. This was put to a vote at a meeting in March 1995 (Austin). As documented in N0661, the unsigned long
constructor for bitset
was not made explicit
(unanimous vote, but without motivation).
However, even though bitset
is easily initialized from unsigned long
, there is otherwise incomplete mixed-mode setwise operations (&
, |
or ^
):
constexpr auto N = 512;
std::bitset<N> b = 0xDEADC0DE; // OK
std::bitset<N> c = b & 0xFFFF; // ERROR, cannot deduce template arguments for rhs
This could be remedied by proposing overloaded operators to support mixed-mode bit-twiddling:
// @ from { &, |, ^ }
template<std::size_t N>
bitset<N> operator@(unsigned long long lhs, const bitset<N>& rhs)
template<std::size_t N>
bitset<N> operator@(const bitset<N>& lhs, unsigned long long rhs)
The schizophrenic nature of std::bitset
with respect to mixed-mode functionality is also present in the operator==
and operator!=
. These are member functions that have implicit conversion on their rhs arguments, but not on their lhs argument (the this
pointer, which is subject to template argument deduction). This leads to the following:
#include <bitset>
#include <iostream>
int main()
{
constexpr auto N = 64;
constexpr std::bitset<N> b = 0xDEADC0DE; // OK, copy initialization
std::cout << (b == 0xDEADC0DE); // OK, implicit conversion on rhs
std::cout << (0xDEADC0DE == b); // ERROR, no implicit conversion on lhs
}
The origins of this behavior stem from the 1992 proposal N0128. The timing of that proposal, which largely locked in the functionality of the future std::bitset
, was prior to function templates having non-type template parameters. The only feasible workaround at the time was to make all overloaded operators member functions instead of non-member functions. This was never changed later on when more advanced template technology became available (see also this Q&A for why this might break code).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With