STL Breaking Changes in Visual Studio 2010 Beta 1

STL Breaking Changes in Visual Studio 2010 Beta 1

Rate This
  • Comments 13

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 Files\Microsoft Visual Studio 10.0\VC\INCLUDE\vector(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 Files\Microsoft Visual Studio 10.0\VC\INCLUDE\vector(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 Files\Microsoft Visual Studio 10.0\VC\INCLUDE\vector(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.

 

 

Problem 3: error C2662: 'NamedNumber::change_name' : cannot convert 'this' pointer from 'const NamedNumber' to 'NamedNumber &'

 

This program compiles and runs cleanly with VC9 SP1:

 

C:\Temp>type std_set.cpp

#include <iostream>

#include <ostream>

#include <set>

#include <string>

using namespace std;

 

class NamedNumber {

public:

    NamedNumber(const string& s, const int n)

        : m_name(s), m_num(n) { }

 

    bool operator<(const NamedNumber& other) const {

        return m_num < other.m_num;

    }

 

    string name() const {

        return m_name;

    }

 

    int num() const {

        return m_num;

    }

 

    void change_name(const string& s) {

        m_name = s;

    }

 

private:

    string m_name;

    int m_num;

};

 

void print(const set<NamedNumber>& s) {

    for (set<NamedNumber>::const_iterator i = s.begin(); i != s.end(); ++i) {

        cout << i->name() << ", " << i->num() << endl;

    }

}

 

int main() {

    set<NamedNumber> s;

 

    s.insert(NamedNumber("Hardy", 1729));

    s.insert(NamedNumber("Fermat", 65537));

    s.insert(NamedNumber("Sophie Germain", 89));

 

    print(s);

 

    cout << "--" << endl;

 

    set<NamedNumber>::iterator i = s.find(NamedNumber("Whatever", 1729));

 

    if (i == s.end()) {

        cout << "OH NO" << endl;

    } else {

        i->change_name("Ramanujan");

    }

 

    print(s);

}

 

C:\Temp>cl /EHsc /nologo /W4 std_set.cpp

std_set.cpp

 

C:\Temp>std_set

Sophie Germain, 89

Hardy, 1729

Fermat, 65537

--

Sophie Germain, 89

Ramanujan, 1729

Fermat, 65537

 

But it fails to compile with VC10 Beta 1:

 

C:\Temp>cl /EHsc /nologo /W4 std_set.cpp

std_set.cpp

std_set.cpp(55) : error C2662: 'NamedNumber::change_name' : cannot convert 'this' pointer from 'const NamedNumber' to 'NamedNumber &'

        Conversion loses qualifiers

 

What's wrong?

 

Solution: Respect set Immutability

 

The problem is modifying set/multiset elements.

 

In C++98/03, you could get away with modifying set/multiset elements as long as you didn't change their ordering.  (Actually changing their ordering is definitely crashtrocity, breaking the data structure's invariants.)

 

C++0x rightly decided that this was really dangerous and wrong.  Instead, it flat-out says that "Keys in an associative container are immutable" (N2857 23.2.4/5) and "For [set and multiset], both iterator and const_iterator are constant iterators" (/6).

 

VC10 Beta 1 enforces the C++0x rules.

 

There are many alternatives to modifying set/multiset elements.

 

·        You can use map/multimap, separating the immutable key and modifiable value parts.

 

·        You can copy, modify, erase(), and re-insert() elements.  (Keep exception safety and iterator invalidation in mind.)

 

·        You can use set/multiset<shared_ptr<T>, comparator>, being careful to preserve the ordering and proving once again that anything can be solved with an extra layer of indirection.

 

·        You can use mutable members (weird) or const_cast (evil), being careful to preserve the ordering.  I strongly recommend against this.

 

I should probably mention, before someone else discovers it, that in VC10 Beta 1 we've got a macro called _HAS_IMMUTABLE_SETS .  Defining it to 0 project-wide will prevent this C++0x rule from being enforced.  However, I should also mention that _HAS_IMMUTABLE_SETS is going to be removed after Beta 1.  You can use it as a temporary workaround, but not as a permanent one.

 

 

Problem 4: Specializing stdext::hash_compare

 

If you've used the non-Standard <hash_set> or <hash_map> and specialized stdext::hash_compare for your own types, this won't work anymore, because we've moved it to namespace std.  <hash_set> and <hash_map> are still non-Standard, but putting them in namespace stdext wasn't accomplishing very much.

 

Solution: Use <unordered_set> or <unordered_map>

 

TR1/C++0x <unordered_set> and <unordered_map> are powered by the same machinery as <hash_set> and <hash_map>, but the unordered containers have a superior modern interface.  In particular, providing hash and equality functors is easier.

 

If you still want to use <hash_set> and <hash_map>, you can specialize std::hash_compare, which is where it now lives.  Or you can provide your own traits class.

 

By the way, for those specializing TR1/C++0x components, you should be aware that they still live in std::tr1 and are dragged into std with using-declarations.  Eventually (after VC10) this will change.

 

 

This isn't an exhaustive list, but these are the most common issues that we've encountered.  Now that you know about them, your upgrading experience should be more pleasant.

 

Stephan T. Lavavej

Visual C++ Libraries Developer

  • PingBack from http://asp-net-hosting.simplynetdev.com/stl-breaking-changes-in-visual-studio-2010-beta-1/

  • Problem 1 begins to make the case for revamping the inclusion mechanism in the language.

  • About Problem 4.

    In spite of recognizing it as hash_set and hash_map being a non-standard, why is it that they are in std namespace?

    They should be in stdext namespace at least.

  • Does Problem 2 mean that we can't normaly declare a vector of shared_ptr? ( e.g. std::vector< std::shared_ptr< CSomething > > ?

  • Are you sure that the STL prohibits overriding operator&()? I was under the impression that it simply required it to have the usual address-of semantics. I remember looking this up because there used to be a bug in the VC++ STL where one of the containers used & on an uninitialized element and would crash if you implemented an overloaded virtual operator&().

  • [blah]

    > Problem 1 begins to make the case for revamping the

    > inclusion mechanism in the language.

    If I use std::vector without including <vector>, is my code broken or is the language broken?

    If I use std::string without including <string>, is my code broken or is the language broken?

    If I use std::back_inserter() without including <iterator>, is my code broken or is the language broken?

    (The second example is subtle, and not new to VC10. In our implementation, std::string (if you know enough to object that it's a typedef, you know enough to ignore this simplification) is defined by the internal header <xstring>. The public header <string> includes <xstring> and additionally defines non-member operators. Some public headers end up dragging in <xstring> but not <string>. As a result, novice users are occasionally confused when they can use std::string but not its non-member operators like op+().)

    This is a problem, but it's not a problem with the header model, it's a problem with education. "Include what you use" is not exactly rocket science.

    [zak]

    > In spite of recognizing it as hash_set and hash_map

    > being a non-standard, why is it that they are in

    > std namespace?

    > They should be in stdext namespace at least.

    This change was driven by Dinkumware, and we deferred to their judgement.

    Basically, since hash_foo is superseded by TR1/C++0x unordered_foo, I find it hard to care about the former (and I'd like to remove it someday).

    But if people are going to use hash_foo, there are cases for and against putting it in std. People who don't want any non-Standard machinery simply won't include <hash_foo> (and if some other header is dragging it in, they can use the usual set of techniques to isolate it in a translation unit). People who want non-Standard machinery and want to "using namespace std;" and get everything will love this change. People who want non-Standard machinery, yet don't want it in std (e.g. because they are "using namespace std;" and defining their own hash_foo elsewhere), will dislike this change - however, hash_foo is a long-established name, with hopefully few imitators (in fact, it was long-established enough that the TR1/C++0x machinery was renamed to avoid colliding). And people with existing codebases who are broken by this change will obviously dislike it; however, the necessary update is simple.

    If this is a huge problem for you, file a bug through Microsoft Connect and we'll consider it.

    [Serpico]

    > Does Problem 2 mean that we can't normaly declare

    > a vector of shared_ptr? ( e.g.

    > std::vector< std::shared_ptr< CSomething > > ?

    That is perfectly legitimate; shared_ptr acts as an "insulating layer" just like CAdapt does.

    [Phaeron]

    > Are you sure that the STL prohibits overriding

    > operator&()? I was under the impression that it

    > simply required it to have the usual address-of

    > semantics.

    I simplified matters slightly. The Standardese is that containers require their elements to be CopyConstructible and Assignable (C++03 23.1/3), and being CopyConstructible requires that &t return a T * that is the address of t (20.1.3/1). So, you can overload operator&(), as long as you return the real address and don't do anything freaky. This puts you into "possibly brittle and definitely weird, but not forbidden" territory. More brittle and more weird if it's virtual.

  • [STL]

    > If I use std::vector without including <vector>, is my code broken or is the language broken?

    IMO?  Likely both.  Your code is broken for not including what it uses, and the language is broken for letting you get away with it.

    Is there a good discussion somewhere about why this isn't locked down?  Even if it would impact compilation speed enough to be a problem for regular use, it seems like compiler vendors could eliminate a lot of portability pain by supporting it as an optional warning.

  • Why dont you guys just drop C++ from Visual Studio? Seriously. You've dropped other languages

    before.

    You keep promising "exciting new features" or "this and that" for C++ when even basic editor tools like intellisense are terrible and refactoring is non-existant.

    Do you think your customers are idiots? Because you do treat us like that.

    Stop delivering half-baked products. Either do it well or don't do it at all.

  • Alec: I don't think that it is Stephan T. Lavavej fault since he have very little to do with the IDE itself, but I agree with you.

    We keep hearing these promises of C++ utopias in every new version, and nothing...also "VS20 is the new VS6" they said..well if that it is true, then I am the pope.

    My conspiracy theory on that would be that MS is not focusing any effort on tools support (like refactoring) in order to please and not kill existing ISVs that develop such tools.

  • 링크 STL Breaking Changes in Visual Studio 2010 Beta 1

  • Is vector<bool> still gonna be a specialization?

  • Yes, vector<bool> is still an explicit specialization. This hasn't changed in C++0x.

  • While some backwards compatibility problems arise from improved standard conformance (or an improved standard to conform to!) I'm surprised at the decision to break code relying on implicit headers.

    Personally I prefer the 'strict header' model and I see this as a desirable new feature of the library, but how much would it have cost to add an additions config macro STRICT_STD_HEADERS, and conditionally include the extra headers as before when this is defined?  This provides a painless upgrade path for users (default to on this release, user must specify next time around) and is not too hard to implement - of course I have no idea what impact would be on your test team.

Page 1 of 1 (13 items)