Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is the std::bitset constructor with an unsigned long long argument not marked as explicit?

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?

like image 860
TemplateRex Avatar asked Sep 14 '14 12:09

TemplateRex


1 Answers

Explicit construction

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).

Mixed-mode bit-twiddling

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)

Overloaded operators as member functions

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).

like image 170
TemplateRex Avatar answered Nov 16 '22 14:11

TemplateRex