views:

773

answers:

6

Consider the following code:

#include <vector>

struct A
{
    explicit A(int i_) : i(i_) {}
    int i;
};

int main()
{
    std::vector<int> ints;
    std::vector<A> As(ints.begin(), ints.end());
}

Should the above compile? My feeling is that it should not, due to the constructor being marked explicit.

Microsoft Visual C++ agrees, giving a clear error message: cannot convert from 'int' to 'const A'; Constructor for struct 'A' is declared 'explicit'

However, using Comeau's online compiler, the code compiles successfully.

Which is correct?

Edit:

Interestingly, changing vector to set (after adding an operator < to A) causes both compilers to give an error.

However, changing vector<int> to map<int, int> and vector<A> to map<A, A> causes both compilers to accept the code!

+1  A: 

I think it would depend on how std::vector<A> As(Iterator,Iterator) is implemented in your particular implementation of the STL.

Nicolás
The behavior of the standard library is not supposed to be dependent on the implementation.
anon
Then again, there are places (very rare) where the standard can be interpreted in two not equivalent ways.
David Rodríguez - dribeas
A: 

This code does not compile in Comeau:

class Foo
{
public:
 explicit Foo(int bar)
 {
 }
};

class Bar
{
 void DoStuff(Foo foo){

 }
 void DoStuff2()
 {
  DoStuff(4);
 }
};

Error message:

"ComeauTest.c", line 16: error: no suitable constructor exists to convert from "int"
          to "Foo"
    DoStuff(4);
            ^

1 error detected in the compilation of "ComeauTest.c".

So at the rudimentary level the online compiler supports explicit constructors. Must be something to do with the vector/iterators.

EDIT This however compiles:

Foo foo = (Foo)5;

Which is an explicit conversion, so that's OK. My guess the Comeau vector class does an explicit cast in the constructor somewhere, where's Microsoft's library doesn't.

More on explicit constructors - http://www.glenmccl.com/tip_023.htm

Igor Zevaka
A: 

Yes it should compile. If the constructor is not used, then its explicitness is not an issue.

anon
If the constructor is not being used, how else are we converting from an int to an A?
Paul Baker
Nothing is being created and nothing is being converted - both vectors are empty
anon
The behaviour is the same if something is inserted into the "ints" vector before the construction of "As".
Paul Baker
+1  A: 

This is a rather tricky question, and it might be the case that VisualStudio is right and Comeau wrong (this seems really hard to believe).

The standard if read word by word, defines that vector constructor in terms of the copy constructor (see quote), and that literally means that the object obtained by dereferencing the iterator must first be converted into the type T and then the copy constructor should be called. At this point, with an explicit constructor the code should not compile.

It seems reasonable to expect an implementation, on the other hand, to directly call a constructor taking as argument the dereferenced iterator, in which case the constructor call would be explicit and thus the code should compile. This would go against the exact wording in the quote below, as the copy constructor is defined for a given type T as a constructor that taking a single possibly constant reference to an object of type T.

I cannot think of any reasonable argument not to use the Comeau approach, and my believe (this is just personal opinion) is that the wording in the standard with respect to the complexity of the vector constructor should probably be restated as requiring only N calls to the appropriate T constructor, where appropriate would have to be defined as the constructor that matches the call T( *first ) (that is, either a constructor taking an InputIterator::value_type (by value or possibly constant reference), or the T copy constructor after an implicit conversion from InputIterator::value_type to T.

23.2.4.1 [lib.vector.cons]/1

Complexity: The constructor template vector(InputIterator first, InputIterator last) makes only N calls to the copy constructor of T (where N is the distance between first and last) and no reallocations if iterators first and last are of forward, bidirectional, or random access categories. It makes order N calls to the copy constructor of T and order log N reallocations if they are just input iterators.

I would like to know how the VS compiler behaves when given:

struct T1;
struct T2 {
   operator T1 ();
};
struct T1 {
   T1( T2 const & ) { std::cout << "T1(T2)" << std::endl; }
};
T2::operator T1() {
   std::cout << "T2::operator T1" << std::endl;
   return T1(*this);
}
int main() {
   std::vector<T2> v2;
   v2.push_back( T2() );
   std::vector<T1> v1( v2.begin(), v2.end() );
}

With g++ the result is that T2::operator T1 is not called, but rather the elements in v1 are constructed directly from the elements in v2. I would assume that with VS the compiler would use T2::operator T1 to convert from each element in v2 to a T1 element and then call the copy constructor. Is that so?

David Rodríguez - dribeas
Paul Baker
David Rodríguez - dribeas
+1  A: 

This really boils down to a question of how the STL library is implemented, not a language specification issue. There is nothing in the language spec that would prohibit this from working, nor is there anything that would require that it should work.

If the stl::vector constructor were written to try an implicit conversion using the assignment operator, then it would fail. It is more likely that the Microsoft STL implementation makes use of return-value optimization during initialization via a constructor call, in which case this code would work fine.

It is important to note that the only reason this works is because the stl::vector constructor is templated, and the only requirement is that it is an input_iterator, or more accurately that it supports all the required functionality of an input iterator.

I'd also like to point out that this is a prime example of why it is often difficult to write cross-platform code. Sometimes you end up with issues where neither compiler necessarily deviates from the language standard, but code still isn't portable.

Chris
+1  A: 

I looked through GCC's STL implementation and it should have similar behavior. Here's why.

  • Elements of a vector are initialized by a generic function template which accepts any two types X and V and calls new( p ) X( v ) where v is a V (I'm paraphrasing a bit). This allows explicit conversion.
  • Elements of a set or map are initialized by a private member function of _tree<T,…> which specifically expects a T const & to be passed in. This member function isn't a template (beyond being a member of a template), so if the initial value can't be implicitly converted to T, the call fails. (Again I'm simplifying the code.)

The standard doesn't require that explicit conversion work or that implicit conversion not work when initializing a container with a range. It simply says that the range is copied into the container. Definitely ambiguous for your purpose.

Surprising such ambiguity exists, considering how they've already refined the standard in consideration of problems like the one I had a couple weeks ago.

Potatoswatter