May 25th, 2009

STL Breaking Changes in Visual Studio 2010 Beta 1

Visual Studio 2010 Beta 1 is now available for download.  I’ve recently blogged about how Visual C++ in VS 2010 Beta 1, which I refer to as VC10 Beta 1, contains compiler support for five C++0x core language features: lambdas, auto, static_assert, rvalue references, and decltype.  It also contains a substantially rewritten implementation of the C++ Standard Library, supporting many C++0x standard library features.  In the near future, I’ll blog about them in Part 4 and beyond of “C++0x Features in VC10”, but today I’m going to talk about the STL changes that have the potential to break existing code, which you’ll probably want to know about before playing with the C++0x goodies.

 

 

Problem 1: error C3861: ‘back_inserter’: identifier not found

 

This program compiles and runs cleanly with VC9 SP1:

 

C:Temp>type back_inserter.cpp

#include <algorithm>

#include <iostream>

#include <ostream>

#include <vector>

using namespace std;

 

int square(const int n) {

    return n * n;

}

 

int main() {

    vector<int> v;

    v.push_back(11);

    v.push_back(22);

    v.push_back(33);

 

    vector<int> dest;

 

    transform(v.begin(), v.end(), back_inserter(dest), square);

 

    for (vector<int>::const_iterator i = dest.begin(); i != dest.end(); ++i) {

        cout << *i << endl;

    }

}

 

C:Temp>cl /EHsc /nologo /W4 back_inserter.cpp

back_inserter.cpp

 

C:Temp>back_inserter

121

484

1089

 

But it fails to compile with VC10 Beta 1:

 

C:Temp>cl /EHsc /nologo /W4 back_inserter.cpp

back_inserter.cpp

back_inserter.cpp(19) : error C3861: ‘back_inserter’: identifier not found

 

What’s wrong?

 

Solution: #include <iterator>

 

The problem was that back_inserter() was used without including <iterator>.  The C++ Standard Library headers include one another in unspecified ways.  “Unspecified” means that the Standard allows but doesn’t require any header X to include any header Y.  Furthermore, implementations (like Visual C++) aren’t required to document what they do, and are allowed to change what they do from version to version (or according to the phase of the moon, or anything else).  That’s what happened here.  In VC9 SP1, including <algorithm> dragged in <iterator>.  In VC10 Beta 1, <algorithm> doesn’t drag in <iterator>.

 

When you use a C++ Standard Library component, you should be careful to include its header (i.e. the header that the Standard says it’s supposed to live in).  This makes your code portable and immune to implementation changes like this one.

 

There are probably more places where headers have stopped dragging in other headers, but <iterator> is overwhelmingly the most popular header that people have forgotten to include.

 

Note: Range Insertion and Range Construction

 

By the way, when seq is a vector, deque, or list, instead of writing this:

 

copy(first, last, back_inserter(seq)); // Bad!

 

You should write this:

 

seq.insert(seq.end(), first, last); // Range Insertion – Good!

 

Or, if you’re constructing seq, simply write this:

 

vector<T> seq(first, last); // Range Construction – Good!

 

They’re not only slightly less typing, they’re also significantly more efficient.  copy()-to-back_inserter() calls push_back() repeatedly, which can trigger multiple vector reallocations.  Given forward or better iterators, range insertion and range construction can just count how many elements you’ve got, and allocate enough space for all of them all at once.  This is also more efficient for deque, and you may as well do it for list too.

 

 

Problem 2: error C2664: ‘std::vector<_Ty>::_Inside’ : cannot convert parameter 1 from ‘IUnknown **’ to ‘const ATL::CComPtr<T> *’

 

This program compiles and runs cleanly with VC9 SP1:

 

C:Temp>type vector_ccomptr.cpp

#include <atlcomcli.h>

#include <stddef.h>

#include <iostream>

#include <ostream>

#include <vector>

using namespace std;

 

int main() {

    vector<CComPtr<IUnknown>> v;

 

    v.push_back(NULL);

}

 

C:Temp>cl /EHsc /nologo /W4 vector_ccomptr.cpp

vector_ccomptr.cpp

 

C:Temp>vector_ccomptr

 

C:Temp>

 

But it fails to compile with VC10 Beta 1:

 

C:Temp>cl /EHsc /nologo /W4 vector_ccomptr.cpp

vector_ccomptr.cpp

C:Program FilesMicrosoft Visual Studio 10.0VCINCLUDEvector(623) : error C2664: ‘std::vector<_Ty>::_Inside’ : cannot convert parameter 1 from ‘IUnknown **’ to ‘const ATL::CComPtr<T> *’

        with

        [

            _Ty=ATL::CComPtr<IUnknown>

        ]

        and

        [

            T=IUnknown

        ]

        Types pointed to are unrelated; conversion requires reinterpret_cast, C-style cast or function-style cast

        C:Program FilesMicrosoft Visual Studio 10.0VCINCLUDEvector(622) : while compiling class template member function ‘void std::vector<_Ty>::push_back(_Ty &&)’

        with

        [

            _Ty=ATL::CComPtr<IUnknown>

        ]

        vector_ccomptr.cpp(9) : see reference to class template instantiation ‘std::vector<_Ty>’ being compiled

        with

        [

            _Ty=ATL::CComPtr<IUnknown>

        ]

C:Program FilesMicrosoft Visual Studio 10.0VCINCLUDEvector(625) : error C2040: ‘-‘ : ‘IUnknown **’ differs in levels of indirection from ‘ATL::CComPtr<T> *’

        with

        [

            T=IUnknown

        ]

 

What’s wrong?

 

Solution: Use CAdapt

 

The Standard containers prohibit their elements from overloading the address-of operator.  CComPtr overloads the address-of operator.  Therefore, vector<CComPtr<T>> is forbidden (it triggers undefined behavior).  It happened to work in VC9 SP1, but it doesn’t in VC10 Beta 1.  That’s because vector now uses the address-of operator in push_back(), among other places.

 

The solution is to use <atlcomcli.h>’s CAdapt, whose only purpose in life is to wrap address-of-overloading types for consumption by Standard containers.  vector<CAdapt<CComPtr<T>>> will compile just fine.  In VC10 Beta 1, I added operator->() to CAdapt, allowing v[i]->Something() to compile unchanged.  However, typically you’ll have to make a few other changes when adding CAdapt to your program.  operator.() can’t be overloaded, so if you’re calling CComPtr‘s member functions like Release(), you’ll need to go through CAdapt‘s public data member m_T .  For example, v[i].Release() needs to be transformed into v[i].m_T.Release() .  Also, if you’re relying on implicit conversions, CAdapt adds an extra layer, which will interfere with them.  Therefore, you may need to explicitly convert things when pushing them back into the vector

Category
C++

0 comments

Discussion are closed.