Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ explicit constructors and iterators

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!

like image 519
user200783 Avatar asked Jan 05 '10 00:01

user200783


People also ask

How do you call an explicit constructor?

Explicit use of the this() or super() keywords allows you to call a non-default constructor. To call a non-args default constructor or an overloaded constructor from within the same class, use the this() keyword. To call a non-default superclass constructor from a subclass, use the super() keyword.

What are STL iterators?

An iterator is used to point to the memory address of the STL container classes. For better understanding, you can relate them with a pointer, to some extent. Iterators act as a bridge that connects algorithms to STL containers and allows the modifications of the data present inside the container.

What is implicit constructor in C++?

implicit constructor is a term commonly used to talk about two different concepts in the language, the. implicitly declared constructor which is a default or copy constructor that will be declared for all user classes if no user defined constructor is provided (default) or no copy constructor is provided (copy).

What are vector iterators?

An iterator is used to move thru the elements an STL container (vector, list, set, map, ...) in a similar way to array indexes or pointers. The * operator dereferences an iterator (ie, is used to access the element an iterator points to) , and ++ (and -- for most iterators) increments to the next element.


3 Answers

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.

like image 86
Potatoswatter Avatar answered Oct 16 '22 11:10

Potatoswatter


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

like image 37
Nicolás Avatar answered Oct 16 '22 11:10

Nicolás


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?

like image 28
David Rodríguez - dribeas Avatar answered Oct 16 '22 10:10

David Rodríguez - dribeas