Are additional constructors for standard containers allowed?

  • Thread starter Alf P. Steinbach /Usenet
  • Start date
A

Alf P. Steinbach /Usenet

With MSVC 10.0

std::bitset<32>( 666u )

does not compile. Apparently due to an extra constructor taking 'int' argument.
Is that allowed by the standard?



Cheers,

- Alf
 
V

Victor Bazarov

With MSVC 10.0

std::bitset<32>( 666u )

does not compile. Apparently due to an extra constructor taking 'int'
argument. Is that allowed by the standard?

I haven't found any wording in the Standard *prohibiting* any C++
Standard Library implementor from supplying more functionality than
specified in the Standard, like more c-tors, extra member functions,
etc. Of course, if code that would otherwise be legal becomes
ill-formed when those new parts are added, it becomes QoI issue.
Complain to the vendor.

V
 
J

Johannes Schaub (litb)

Alf said:
With MSVC 10.0

std::bitset<32>( 666u )

does not compile. Apparently due to an extra constructor taking 'int'
argument. Is that allowed by the standard?

Just like adding template parameters to a template is disallowed (even if
all of them have defaults) because it makes valid code invalid, this one
would be invalid aswell, i think. Haven't got any chap/verse though.
 
M

Michael Doubez

I haven't found any wording in the Standard *prohibiting* any C++
Standard Library implementor from supplying more functionality than
specified in the Standard, like more c-tors, extra member functions,
etc.  Of course, if code that would otherwise be legal becomes
ill-formed when those new parts are added, it becomes QoI issue.
Complain to the vendor.

IMHO clause 1.4/3 allows it:
"For classes and class templates, the library clauses specify partial
definitions.[...]"

But there is a tension with clause 1.4/8:
"A conforming implementation may have extensions, provided they do not
alter the behavior of any well-formed program.[...]"

Which mean that if a program is well formed according to the standard,
it should be well formed according to a compliant implementation
(which, I guess, is the purpose of a standard).

IMO, since std::bitset<32>( 666u ) is well formed thanks to integral
promotion, a compiler refusing it should be non-conformant.

Apart from that, it is sometimes useful like implementations of
std::string class that declare a private constructor taking a boolean
in order to avoid integral promotion of std::string(false).
 
N

Niels Dekker - no reply address

Alf said:
With MSVC 10.0
std::bitset<32>( 666u )
does not compile. Apparently due to an extra constructor taking 'int'
argument. Is that allowed by the standard?

Victor said:
[...] Of course, if code that would otherwise be legal becomes
ill-formed when those new parts are added, it becomes QoI issue.
Complain to the vendor.

This issue has been reported already, by Richard Webb: "Problems
constructing a bitset from an unsigned long in the VC RC",
http://connect.microsoft.com/VisualStudio/feedback/details/532897

The extra contructor bitset(int) was there to fix another issue, by jkolb1,
"bitset<5> bits(0) fails with conflict between longlong and char*",
http://connect.microsoft.com/VisualStudio/feedback/details/500122

Which is submitted as Standard Library issue #1325, by Christopher
Jefferson: http://www.open-std.org/JTC1/sc22/WG21/docs/lwg-active.html#1325


HTH,

Niels
 
A

Alf P. Steinbach /Usenet

* Niels Dekker - no reply address, on 03.09.2010 19:20:
Alf said:
With MSVC 10.0
std::bitset<32>( 666u )
does not compile. Apparently due to an extra constructor taking 'int'
argument. Is that allowed by the standard?

Victor said:
[...] Of course, if code that would otherwise be legal becomes
ill-formed when those new parts are added, it becomes QoI issue.
Complain to the vendor.

This issue has been reported already, by Richard Webb: "Problems
constructing a bitset from an unsigned long in the VC RC",
http://connect.microsoft.com/VisualStudio/feedback/details/532897

The extra contructor bitset(int) was there to fix another issue, by jkolb1,
"bitset<5> bits(0) fails with conflict between longlong and char*",
http://connect.microsoft.com/VisualStudio/feedback/details/500122

Which is submitted as Standard Library issue #1325, by Christopher
Jefferson: http://www.open-std.org/JTC1/sc22/WG21/docs/lwg-active.html#1325

Oh my. SNAFU strikes again. :)

From the proposed resolution,

<code>
template <class charT>
explicit
bitset(const charT *str,
typename basic_string<charT>::size_type pos = 0,
typename basic_string<charT>::size_type n =
basic_string<charT>::npos,
charT zero = charT('0'), charT one = charT('1'));
</code>

Here charT('0') is not formally guaranteed to represent L'0' when charT is wchar_t.

Of course it works for Unicode, when the original character set isn't EBCDIC.

Oh holy Odin! And Tor! That ungood cast is actually in the C++0x draft!

If only the committee made a habit of consulting me every time they got an urge
to introduce fancy over-engineered "solutions". But no. Never heard from them.

Anyways, if anyone of them should happen to listen in, the Correct(TM) thing to
do is to /ditch/ that over-engineering -- a hammer does not need to also be a
radio receiver and refrigerator, d'u hear? -- and is otherwise exemplified by


<code>
#include <locale>
#include <iostream>

template< class CharT >
int foo( CharT c = std::use_facet< std::ctype< char > >( std::locale::classic()
).widen( '0' ) )
{
return c;
}

int main()
{
std::cout << foo<wchar_t>() << std::endl;
}
</code>


<deadpan>This clear and concise code is why I love the standard lib's
localization handling.</deadpan>

But oops, this does not compile with MSVC 10.0... :-(


Cheers,

- Alf
 
J

Juha Nieminen

Niels Dekker - no reply address said:
The extra contructor bitset(int) was there to fix another issue, by jkolb1,
"bitset<5> bits(0) fails with conflict between longlong and char*",

One could argue that giving 0 as parameter to the std::bitset
constructor makes little sense. As a char* it would be invalid and
cause UB, and as an unsigned long it would be the same as not giving
anything at all (because the default constructor initializes all the
bits to zero already).

I can't think of a rational situation where one would end up giving
the *literal* 0 to the bitset constructor (giving it a an integral
variable, possibly a const, wouldn't cause a problem because its type
is unambiguous).

Maybe if one #defines the initial value of the bitset as a preprocessor
macro... Still feels a bit contrived, though.
 
N

Niels Dekker - no reply address

The extra contructor bitset(int) was there to fix another issue, by
Juha said:
One could argue that giving 0 as parameter to the std::bitset
constructor makes little sense. As a char* it would be invalid and
cause UB, and as an unsigned long it would be the same as not giving
anything at all (because the default constructor initializes all the
bits to zero already).

I can't think of a rational situation where one would end up giving
the *literal* 0 to the bitset constructor (giving it a an integral
variable, possibly a const, wouldn't cause a problem because its type
is unambiguous).

What do you mean by "possibly a const"? The following is still
ambiguous, according to the current Working Draft (N3126):

const int zeroConst = 0;
// bitset(unsigned long long) or bitset(const char*)?
std::bitset said:
Maybe if one #defines the initial value of the bitset as a
preprocessor macro... Still feels a bit contrived, though.

I have to admit I originally didn't take this use case very serious,
passing a zero-valued constant expression as constructor argument to
std::bitset. But then I realized again, that backward compatibility is
*very* important to C++. And the use case is entirely legal in C++03.
Compilers don't even warn you against it!

So I think LWG issue #1325 ("bitset") needs to be fixed.


Kind regards, Niels
 
P

Pavel

Juha said:
One could argue that giving 0 as parameter to the std::bitset
constructor makes little sense. As a char* it would be invalid and
cause UB, and as an unsigned long it would be the same as not giving
anything at all (because the default constructor initializes all the
bits to zero already).

I can't think of a rational situation where one would end up giving
the *literal* 0 to the bitset constructor
To initialize a static member of a class template specialization?

For a bitset, you could do = std::bitset(), of course but (0) is shorter.

-Pavel
 
J

joe

Alf P. Steinbach /Usenet wrote:>

"Are additional constructors for standard containers allowed?"

I hope not.
 
N

Niels Dekker - no reply address

Pavel said:
To initialize a static member of a class template specialization?

For a bitset, you could do = std::bitset(), of course but (0) is
shorter.

Thanks Pavel! I think that's a very convincing use case, especially in
case of a default argument:

int some_func( std::bitset<5> arg = 0 );

But do you think "bitset<N> = NULL" should also compile?

int some_func( std::bitset<5> arg = NULL ); // ???


IMO the Standard does not need to specify whether "bitset<N> = NULL"
compiles, but I certainly wouldn't appreciate undefined behavior!

Kind regards,

Niels
 
J

Johannes Schaub (litb)

Niels said:
Thanks Pavel! I think that's a very convincing use case, especially in
case of a default argument:

int some_func( std::bitset<5> arg = 0 );

But do you think "bitset<N> = NULL" should also compile?

int some_func( std::bitset<5> arg = NULL ); // ???


IMO the Standard does not need to specify whether "bitset<N> = NULL"
compiles, but I certainly wouldn't appreciate undefined behavior!

For a standard std::bitset<>, using "= NULL" certainly compiles because all
integer types are convertible to unsigned long. But I don't see why you
would use NULL instead of plain 0.

Initializing a static member of an explicit class template specialization
can be done by just letting the default constructor execute without an
explicit initializer in case of 0. The case where you need an explicit
initializer is only when you specialize the static member itself.
 
N

Niels Dekker - no reply address

For a standard std::bitset<>, using "= NULL" certainly compiles
because all integer types are convertible to unsigned long.

NULL does not need to be an integer, right? So I think "std::bitset<N>
= NULL" does not need to compile, according to the C++03 Standard. But
/if/ it compiles, it will pick the bitset(unsigned long) constructor,
indeed. Constructing a bitset of all zero's, as people would expect.
But I don't see why you would use NULL instead of plain 0.

Well, personally I certainly wouldn't pass NULL as constructor argument
to std::bitset. But there might be some legacy code out here that does!

It looks like acording to the latest Working Draft (N3126),
std::bitset<N>(NULL) might trigger undefined behavior, right? Because
NULL might be defined as nullptr, and std::bitset<N>(nullptr) would call
the bitset(const char*) constructor from the Working Draft
([template.bitset]).

Please correct me if I'm wrong!

Kind regards, Niels
 
J

Johannes Schaub (litb)

Niels said:
NULL does not need to be an integer, right? So I think "std::bitset<N>
= NULL" does not need to compile, according to the C++03 Standard. But
/if/ it compiles, it will pick the bitset(unsigned long) constructor,
indeed. Constructing a bitset of all zero's, as people would expect.

In C++03, NULL is guaranteed to be an integer, so it guarantees compilation.
Seems that C++0x would indeed not guarantee this anymore, though.
But I don't see why you would use NULL instead of plain 0.

Well, personally I certainly wouldn't pass NULL as constructor argument
to std::bitset. But there might be some legacy code out here that does!

It looks like acording to the latest Working Draft (N3126),
std::bitset<N>(NULL) might trigger undefined behavior, right? Because
NULL might be defined as nullptr, and std::bitset<N>(nullptr) would call
the bitset(const char*) constructor from the Working Draft
([template.bitset]).

Exactly. I wasn't thinking about C++0x, but it seems that nullptr is a valid
choice, and would make it take the const char* one. The workaround they
thought of in that one issue report to template it and make it take "CharT
const *" would get rid of that issue though.
 
N

Niels Dekker - no reply address

Johannes said:
In C++03, NULL is guaranteed to be an integer, so it guarantees
compilation.

Okay, you're right (My bad!)
It looks like acording to the latest Working Draft (N3126),
std::bitset<N>(NULL) might trigger undefined behavior, right? Because
NULL might be defined as nullptr, and std::bitset<N>(nullptr) would
call the bitset(const char*) constructor from the Working Draft
([template.bitset]).

Exactly. I wasn't thinking about C++0x, but it seems that nullptr is
a valid choice, and would make it take the const char* one. The
workaround they thought of in that one issue report to template it
and make it take "CharT const *" would get rid of that issue though.

Hope you like the proposed resolution :)
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2010/n3133.html#1325


Kind regards, Niels
 
A

Alf P. Steinbach /Usenet

* Johannes Schaub (litb), on 05.09.2010 15:56:
Niels said:
NULL does not need to be an integer, right? So I think "std::bitset<N>
= NULL" does not need to compile, according to the C++03 Standard. But
/if/ it compiles, it will pick the bitset(unsigned long) constructor,
indeed. Constructing a bitset of all zero's, as people would expect.

In C++03, NULL is guaranteed to be an integer, so it guarantees compilation.
Seems that C++0x would indeed not guarantee this anymore, though.
But I don't see why you would use NULL instead of plain 0.

Well, personally I certainly wouldn't pass NULL as constructor argument
to std::bitset. But there might be some legacy code out here that does!

It looks like acording to the latest Working Draft (N3126),
std::bitset<N>(NULL) might trigger undefined behavior, right? Because
NULL might be defined as nullptr, and std::bitset<N>(nullptr) would call
the bitset(const char*) constructor from the Working Draft
([template.bitset]).

Exactly. I wasn't thinking about C++0x, but it seems that nullptr is a valid
choice, and would make it take the const char* one. The workaround they
thought of in that one issue report to template it and make it take "CharT
const *" would get rid of that issue though.

Sorry, no.

<code>
#include <iostream>
#include <cstddef> // nullptr_t
using namespace std;

void foo( unsigned long long x )
{
cout << "f(" << x << ")" << endl;
}

// void foo( nullptr_t )
// {
// cout << "nullptr: "; foo( static_cast<unsigned long long>( 0 ) );
// }

template< class CharType >
void foo( CharType const* s )
{
wcout << "f(\"" << s << "\")" << endl;
}

int main()
{
foo( 42 );
foo( 0 );
foo( nullptr );
foo( "Blah" );
}
</code>

<error>
y.cpp(25) : error C2664: 'void foo(unsigned __int64)' : cannot convert parameter
1 from 'nullptr' to 'unsigned __int64'
A native nullptr can only be converted to bool or, using
reinterpret_cast, to an integral type
</error>


Uncommenting the nulltr_t argument overload yields a different error,


<error>
y.cpp(24) : error C2668: 'foo' : ambiguous call to overloaded function
y.cpp(10): could be 'void foo(std::nullptr_t)'
y.cpp(5): or 'void foo(unsigned __int64)'
while trying to match the argument list '(int)'
</error>


One solution if one wants to support nullptr as actual argument is to treat a
literal 0 and 'nullptr' as the same, denoting 0, e.g.


<code>
#include <iostream>
#include <cstddef> // nullptr_t
using namespace std;

template< typename Type >
class Wrapped
{
private:
Type value_;
public:
Wrapped( Type const& v ): value_( v ) {}
Type& value() { return value_; }
Type const& value() const { return value_; }
};

void foo( Wrapped<unsigned long long> x )
{
cout << "f(" << x.value() << ")" << endl;
}

void foo( nullptr_t )
{
cout << "nullptr: "; foo( Wrapped<unsigned long long>( 0 ) );
}

template< class CharType >
void foo( CharType const* s )
{
wcout << "f(\"" << s << "\")" << endl;
}

int main()
{
foo( 42 );
foo( 0 );
foo( nullptr );
foo( "Blah" );
}
</code>

<output>
f(42)
nullptr: f(0)
nullptr: f(0)
f("Blah")
</output>


But I'm more concerned about the incorrect casts in the current draft, and in
the proposed resolution of the 0-argument issue, than support of nullptr.

It's sort of very blatant, with std::endl doing it right and std::bitset doing
it wrong. If one argues that std::bitset casts are OK, then std::endl is
needlessly doing a widening. But I don't think the argument that std::endl is
defined with unneded complexity, holds, or if it does, it should be cleaned...


Cheers,

- Alf
 
J

Johannes Schaub (litb)

Alf said:
* Johannes Schaub (litb), on 05.09.2010 15:56:
Niels said:
But do you think "bitset<N> = NULL" should also compile?
int some_func( std::bitset<5> arg = NULL ); // ???

Johannes Schaub (litb) wrote:
For a standard std::bitset<>, using "= NULL" certainly compiles
because all integer types are convertible to unsigned long.

NULL does not need to be an integer, right? So I think "std::bitset<N>
= NULL" does not need to compile, according to the C++03 Standard. But
/if/ it compiles, it will pick the bitset(unsigned long) constructor,
indeed. Constructing a bitset of all zero's, as people would expect.

In C++03, NULL is guaranteed to be an integer, so it guarantees
compilation. Seems that C++0x would indeed not guarantee this anymore,
though.
But I don't see why you would use NULL instead of plain 0.

Well, personally I certainly wouldn't pass NULL as constructor argument
to std::bitset. But there might be some legacy code out here that does!

It looks like acording to the latest Working Draft (N3126),
std::bitset<N>(NULL) might trigger undefined behavior, right? Because
NULL might be defined as nullptr, and std::bitset<N>(nullptr) would call
the bitset(const char*) constructor from the Working Draft
([template.bitset]).

Exactly. I wasn't thinking about C++0x, but it seems that nullptr is a
valid choice, and would make it take the const char* one. The workaround
they thought of in that one issue report to template it and make it take
"CharT const *" would get rid of that issue though.

Sorry, no.

Oh, my statement "would get rid of that issue" meant to say that it will
fail to compile with the proposed solution. E.g that it won't silently work
and select the "char const*" version if one passes nullptr.
Uncommenting the nulltr_t argument overload yields a different error,


<error>
y.cpp(24) : error C2668: 'foo' : ambiguous call to overloaded function
y.cpp(10): could be 'void foo(std::nullptr_t)'
y.cpp(5): or 'void foo(unsigned __int64)'
while trying to match the argument list '(int)'
</error>

This looks to me like an ugly overload resolution behavior :( I wonder what
the rationale is to allow "integer -> nullptr" ?
But I'm more concerned about the incorrect casts in the current draft, and
in the proposed resolution of the 0-argument issue, than support of
nullptr.

It's sort of very blatant, with std::endl doing it right and std::bitset
doing it wrong. If one argues that std::bitset casts are OK, then
std::endl is needlessly doing a widening. But I don't think the argument
that std::endl is defined with unneded complexity, holds, or if it does,
it should be cleaned...

I don't understand how "std::endl" is connected to this and what 0-argument
issue the proposed resolution has. I thought that resolution *fixes* it than
causing it in the first place? I feel like I'm missing something, could you
please elaborate?
 
A

Alf P. Steinbach /Usenet

* Johannes Schaub (litb), on 05.09.2010 19:13:
Alf said:
* Johannes Schaub (litb), on 05.09.2010 15:56:
Niels Dekker - no reply address wrote:

But do you think "bitset<N> = NULL" should also compile?
int some_func( std::bitset<5> arg = NULL ); // ???

Johannes Schaub (litb) wrote:
For a standard std::bitset<>, using "= NULL" certainly compiles
because all integer types are convertible to unsigned long.

NULL does not need to be an integer, right? So I think "std::bitset<N>
= NULL" does not need to compile, according to the C++03 Standard. But
/if/ it compiles, it will pick the bitset(unsigned long) constructor,
indeed. Constructing a bitset of all zero's, as people would expect.


In C++03, NULL is guaranteed to be an integer, so it guarantees
compilation. Seems that C++0x would indeed not guarantee this anymore,
though.

But I don't see why you would use NULL instead of plain 0.

Well, personally I certainly wouldn't pass NULL as constructor argument
to std::bitset. But there might be some legacy code out here that does!

It looks like acording to the latest Working Draft (N3126),
std::bitset<N>(NULL) might trigger undefined behavior, right? Because
NULL might be defined as nullptr, and std::bitset<N>(nullptr) would call
the bitset(const char*) constructor from the Working Draft
([template.bitset]).

Exactly. I wasn't thinking about C++0x, but it seems that nullptr is a
valid choice, and would make it take the const char* one. The workaround
they thought of in that one issue report to template it and make it take
"CharT const *" would get rid of that issue though.

Sorry, no.

Oh, my statement "would get rid of that issue" meant to say that it will
fail to compile with the proposed solution. E.g that it won't silently work
and select the "char const*" version if one passes nullptr.

Ah well, then it might break old C++98 code using NULL as argument.

Anyway it wouldn't support that.

This looks to me like an ugly overload resolution behavior :( I wonder what
the rationale is to allow "integer -> nullptr" ?

The same as for integral constant 0 -> null pointer of any type?

;-)

I don't understand how "std::endl" is connected to this and what 0-argument
issue the proposed resolution has. I thought that resolution *fixes* it than
causing it in the first place? I feel like I'm missing something, could you
please elaborate?

Yes, the proposed resolution fixes one 0-argument issue.

---

Unrelated to your question (as I interpret it), there is an additional such
issue that it does not fix, namely the one raised by Niels Dekker upthread, that

std::bitset<N> x( NULL );

should perhaps better not be broken, when NULL is of std::nullptr_t.

---

Regarding 'std::endl', its effect is defined as

"Calls os.put(os.widen('\n')), then os.flush()"

Assuming EBCDIC as narrow execution character set '\n' probably resolves to
char(37). For the wchar_t instantiation of endl the call to 'widen' then
converts that to char(10), the ASCII, Latin-1 and Unicode linefeed/newline.

Which is OK.

In the current draft's std::bitset (I'm using N3092), and also in the proposed
resolution of the 0 issue, one constructor has defaulted arguments

"charT zero=charT('0'), charT one=charT('1')"

With EBCDIC narrow character set, '0' resolves to char( 240 ). For the wchar_t
instantiation of the constructor the first cast then produces wchar_t( 240 ).
Assuming Unicode wide character set that's an 'Eth' character, 'ð'.

Which is not OK.


Cheers & hth.,

- Alf
 
J

Johannes Schaub (litb)

Alf said:
* Johannes Schaub (litb), on 05.09.2010 19:13:

Yes, the proposed resolution fixes one 0-argument issue.

---

Unrelated to your question (as I interpret it), there is an additional
such issue that it does not fix, namely the one raised by Niels Dekker
upthread, that

std::bitset<N> x( NULL );

should perhaps better not be broken, when NULL is of std::nullptr_t.

I wonder how many people have written such code and how much code uses such
code. Such code seems to make it hard to modernize stuff :(
---

Regarding 'std::endl', its effect is defined as

"Calls os.put(os.widen('\n')), then os.flush()"

Assuming EBCDIC as narrow execution character set '\n' probably resolves
to char(37). For the wchar_t instantiation of endl the call to 'widen'
then converts that to char(10), the ASCII, Latin-1 and Unicode
linefeed/newline.

Which is OK.

In the current draft's std::bitset (I'm using N3092), and also in the
proposed resolution of the 0 issue, one constructor has defaulted
arguments

"charT zero=charT('0'), charT one=charT('1')"

With EBCDIC narrow character set, '0' resolves to char( 240 ). For the
wchar_t instantiation of the constructor the first cast then produces
wchar_t( 240 ). Assuming Unicode wide character set that's an 'Eth'
character, 'ð'.

Which is not OK.

Ahh thanks for these insights.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,744
Messages
2,569,484
Members
44,903
Latest member
orderPeak8CBDGummies

Latest Threads

Top