Bug 38265 - STL treats explicit constructors as converting constructors
Summary: STL treats explicit constructors as converting constructors
Status: RESOLVED INVALID
Alias: None
Product: gcc
Classification: Unclassified
Component: libstdc++ (show other bugs)
Version: 4.1.2
: P3 normal
Target Milestone: ---
Assignee: Paolo Carlini
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2008-11-25 19:11 UTC by konto.dydaktyczne
Modified: 2015-05-20 11:44 UTC (History)
2 users (show)

See Also:
Host: i686-pc-linux-gnu
Target: i686-pc-linux-gnu
Build: i686-pc-linux-gnu
Known to work:
Known to fail:
Last reconfirmed: 2008-11-28 11:03:38


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description konto.dydaktyczne 2008-11-25 19:11:38 UTC
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);
}
Comment 1 Chris Fairles 2008-11-25 19:34:54 UTC
GCC 4.4.0 also accepts this code as does Comeau 4.3.10.1.
Comment 2 Paolo Carlini 2008-11-25 19:48:00 UTC
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.
Comment 3 konto.dydaktyczne 2008-11-28 10:07:22 UTC
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);
}
Comment 4 Paolo Carlini 2008-11-28 10:50:53 UTC
Yes, this is obvious, just grep for _Construct.
Comment 5 Paolo Carlini 2008-11-28 11:03:38 UTC
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...
Comment 6 Paolo Carlini 2008-11-28 11:17:30 UTC
Hummm, the issue seems different than I remembered it. Will see..
Comment 7 konto.dydaktyczne 2008-11-28 11:23:16 UTC
(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.
Comment 8 Paolo Carlini 2008-11-28 11:30:12 UTC
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...
Comment 9 Paolo Carlini 2008-11-28 11:40:11 UTC
... 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.
Comment 10 konto.dydaktyczne 2008-11-29 16:36:23 UTC
(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.
Comment 11 konto.dydaktyczne 2008-11-29 16:40:39 UTC
(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.

Comment 12 Paolo Carlini 2008-11-29 17:05:55 UTC
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.




>
Comment 13 Jonathan Wakely 2014-05-07 17:28:15 UTC
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.
Comment 14 Geoff Romer 2015-05-19 23:56:25 UTC
> 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).
Comment 15 Jonathan Wakely 2015-05-20 11:44:30 UTC
(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.