When some STL containers are created, explicit constructors for contained objects are treated as converting constructors. The keyword "explicit" is ignored, and no error message is issued; see the code. #include <vector> #include <deque> class X { public: explicit X(int) {} }; int main() { int a[1] = {}; std::vector<X> v(a, a + 1); std::deque<X> d(a, a + 1); }
GCC 4.4.0 also accepts this code as does Comeau 4.3.10.1.
Unless I'm badly mistaken, this behaviour, dating back to the original HP/SGI STL and as such very difficult to change now, is a small extension due to the use in the containers of _Construct instead of calling allocator::construct directly. All in all, at the moment I don't think we have good reasons to change it.
Let me add an std::list and an std::set to my code; see below. Both additions produce errors. So, [sequence containers] std::vector -> no error signaled, "explicit" ignored std::list -> error signaled, because of "explicit" std::deque -> no error signaled, "explicit" ignored [associative containers] std::set -> error signaled, because of "explicit" #include <vector> #include <deque> #include <list> #include <set> class X { public: explicit X(int) {} int operator<(const X&) const { return 0; } }; int main() { int a[1] = {}; std::vector<X> v(a, a + 1); std::deque<X> d(a, a + 1); std::list<X> l(a, a + 1); std::set<X> s(a, a + 1); }
Yes, this is obvious, just grep for _Construct.
But I see now that al long time ago list & co also used _Construct, this is indeed an inconsistency... Let's see what we can do here...
Hummm, the issue seems different than I remembered it. Will see..
(In reply to comment #1) > GCC 4.4.0 also accepts this code as does Comeau 4.3.10.1. Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 15.00.21022.08 for 80x86 does not accept this code.
The issue is different because if I remove all uses of _Construct and implement the various uninitialized_* per the letter of the Standard, nothing changes...
... and I'm coming to the conclusion that this is not a bug in our library. Consider the std::vector case: I cannot see anything wrong with using std::uninitialized_copy in the implementation of the constructor. In turn, the former does, per the Standard: ::new(static_cast<void*>(&*__cur)) typename iterator_traits<_ForwardIterator>::value_type(*__first); thus, the explicit constructor can be used.
(In reply to comment #9) The Standard does not require that std::uninitialized_copy be invoked when constructing an std::vector or std::deque. Moreover, an explicit constructor constructs objects only where the direct-initialization syntax or where casts are explicitly used. None of these cases occurs in the code above.
(In reply to comment #10) > an explicit constructor constructs objects only where the > direct-initialization syntax or where casts are explicitly used. None of these > cases occurs in the code above. I mean the code in the bug description.
Subject: Re: STL treats explicit constructors as converting constructors > The Standard does not require that std::uninitialized_copy be > invoked when > constructing an std::vector or... Sure, but certainly doesn't forbid it, and I maintain is the natural choice in this case. >
The C++11 standard clarifies that this is intended to work. The range constructors require the type to be EmplaceConstructible into the container from the iterator's value_type, which means calling allocator_traits<A>::construct with the iterator's value type. For std::allocator that is a call to placement new using direct-initialization which means explicit constructors are viable. If you don't want the explicit constructor to be viable then you need to use a custom allocator which does something different. (In reply to konto.dydaktyczne from comment #3) > [sequence containers] > std::vector -> no error signaled, "explicit" ignored > std::list -> error signaled, because of "explicit" This now compiles in C++11 mode. > std::deque -> no error signaled, "explicit" ignored > > [associative containers] > std::set -> error signaled, because of "explicit" This still fails, at a different place, but I think it should compile.
> The C++11 standard clarifies that this is intended to work. The range > constructors require the type to be EmplaceConstructible into the container > from the iterator's value_type But see [sequence.reqmts]/p3: "i and j denote iterators satisfying input iterator requirements and refer to elements implicitly convertible to value_type". So this is not "intended to work" per se; a conforming library could disallow it. However, the notes on LWG 536 say "Some support, not universal, for respecting the explicit qualifier", so it looks like the standard intentionally permits this as a conforming extension. In principle, I think "perfect initialization" is what's called for here: the range ctor should be explicit if and only if it uses an explicit constructor for value_type. However, I doubt it's worth the trouble (OTOH, it definitely will be worth the trouble when/if we get single-argument range constructors).
(In reply to Geoff Romer from comment #14) > But see [sequence.reqmts]/p3: "i and j denote iterators satisfying input > iterator requirements and refer to elements implicitly convertible to > value_type". So this is not "intended to work" per se; a conforming library > could disallow it. However, the notes on LWG 536 say "Some support, not > universal, for respecting the explicit qualifier", so it looks like the > standard intentionally permits this as a conforming extension. Which makes the original testcase invalid, so it's a bug in the user's code, not a bug in libstdc++ to accept it. > In principle, I think "perfect initialization" is what's called for here: > the range ctor should be explicit if and only if it uses an explicit > constructor for value_type. I don't think that would be a good idea. I don't think it follows that X(int) being explicit should mean deque<X>(int*, int*) should be explicit. By that logic shared_ptr(unique_ptr<T>&&) should be explicit, because unique_ptr(T*) is explicit. int* is not int, and deque<X> is not X.