Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

std::swap weirdness with G++

This is a weirdness, where I don't know, if it is with the C++ standard, with my compiler (G++ version 4.6.3 on Ubuntu 12.04, which is the latest long-term support version of Ubuntu) or with me, who doesn't understand ;-)

The code in question is as simple as follows:

#include <algorithm>    // for std::swap
void f(void)
{
    class MyClass { };
    MyClass aa, bb;
    std::swap(aa, bb);          // doesn't compile
}

When trying to compile with G++, the compiler yields the following error message:

test.cpp: In function ‘void f()’:
test.cpp:6:21: error: no matching function for call to ‘swap(f()::MyClass&, f()::MyClass&)’
test.cpp:6:21: note: candidates are:
/usr/include/c++/4.6/bits/move.h:122:5: note: template<class _Tp> void std::swap(_Tp&, _Tp&)
/usr/include/c++/4.6/bits/move.h:136:5: note: template<class _Tp, long unsigned int _Nm> void std::swap(_Tp (&)[_Nm], _Tp (&)[_Nm])

The surprising result is, that just moving the class definition out of the function makes that code compile fine:

#include <algorithm>    // for std::swap
class MyClass { };
void f(void)
{
    MyClass aa, bb;
    std::swap(aa, bb);          // compiles fine!
}

So is it, that std::swap() is not supposed to work on classes, which are private to functions? Or is this a bug with G++, maybe the specific version of G++ I am using?

Even more puzzling is, that the following does work again, despite MyListClass is also private (but extends an "official" class, for which maybe a specific implementation of swap() exists):

#include <algorithm>        // for std::swap
#include <list>             // for std::list
void g(void)
{
    class MyListClass : public std::list<int> { };
    MyListClass aa, bb;
    std::swap(aa, bb);              // compiles fine!
}

But just change from objects to pointers, and compiling fails again:

#include <algorithm>        // for std::swap
#include <list>             // for std::list
void g(void)
{
    class MyListClass : public std::list<int> { };
    MyListClass aa, bb;
    MyListClass* aap = &aa;
    MyListClass* bbp = &bb;
    std::swap(aap, bbp);    // doesn't compile!
}

Of course, in my real application the classes are more complex; I simplified the code as much as possible to still reproduce the problem.

like image 834
Kai Petzke Avatar asked Jun 27 '13 14:06

Kai Petzke


1 Answers

If you are running in C++03 mode, which I believe to be the case, you are not allowed to use a locally defined type in a template. If this is the case you can define your types at the namespace level to make it work, or else you can compile in C++11 mode where it should compile.[*]

In case you wonder why the second case works, the standard does not provide specializations of

template <typename T> void swap(T&,T&) // [1] 

as std::list is a template itself and you cannot partially specialize template functions. What it provides is a different base template:

template <typename T, typename A> void swap(list<T,A>&,list<T,A>&); // [2]

Now as in the previous case, the compiler cannot use your local type with [1], so that is discarded. Then it tries [2], and it finds that it can convert the lvalue of the local type to references to the base std::list<int>, and after that conversion [2] is a good candidate. It will then call

std::swap(static_cast<std::list<int&>>(aa),static_cast<std::list<int&>>(bb));

which does not use a local type, but rather the namespace level std::list<int>.

On the other hand, the fact that it compiles does not mean that it does what you want. In particular, if the extended type MyListClass adds any new member variables, those will not be swapped.

All that being said, and just as a side note: you should not inherit from standard containers, as they were never designed to be inherited from.

[*] Disclaimer: I don't know whether this feature is supported in that particular version of the compiler, you will have to double check.

like image 98
David Rodríguez - dribeas Avatar answered Nov 15 '22 17:11

David Rodríguez - dribeas