1

I have found a strange behaviour while trying to complie some code with plain std::list taking a custom allocator. Consider the following code:

std::list<int, std::allocator<int>> theList;
theList.push_back(1);

It's a normal list variable with some data being added. Now if I switch it to:

std::list<int, std::allocator<int>> theList(std::allocator<int>());
theList.push_back(1);

Visual Studio 2012 fails to compile it with "error C2228: left of '.push_back' must have class/struct/union". Of course std::list has a constructor which takes a const reference to an allocator. But if i change it to:

std::list<int, std::allocator<int>> theList = std::list<int, std::allocator<int>>(std::allocator<int>());
theList.push_back(1);

all is fine. Why is the second part failing? To add to the strangeness of the situation, when trying to return theList by value:

typedef std::list<int, std::allocator<int>> TempType;

TempType func()
{
    TempType theList(std::allocator<int>());
    return theList;
}

I get "error C2664: 'std::list<_Ty,_Alloc>::list(const std::list<_Ty,Alloc> &)' : cannot convert parameter 1 from 'TempType (_cdecl *)(std::allocator<Ty> (_cdecl *)(void))' to 'const std::list<_Ty,_Alloc> &'". Looks like the compiler treats the list as a function declaration. Any idea why that could be?

krojew
  • 1,297
  • 15
  • 38
  • isnt list supposed to be of one T, not two? – Ariel Pinchover Apr 17 '13 at 19:31
  • @Infested std::list can take an allocator as the second type – krojew Apr 17 '13 at 19:32
  • I assume that in your real code, `std::list>` is actually written as `std::list >`? (I've not looked more in-depth; not very experienced with allocators) – Dave Apr 17 '13 at 19:33
  • @Dave actually the exact code I posted gives the same errors, so the actual types are irrelevant. The interesting part is that I get the same bahaviour with std::unordered_set. – krojew Apr 17 '13 at 19:35
  • 1
    @Dave: neither needed nor relevant. – PlasmaHH Apr 17 '13 at 19:35
  • @PlasmaHH actually it is needed for C++ pre-C++11: http://stackoverflow.com/a/7087054/1180785 (although I wasn't aware until now that they'd fixed it in the new standard). If you had different behaviour, it was because your compiler was being nice, but your code was non-portable. – Dave Apr 17 '13 at 19:44
  • Can we have a bot which identifies the most vexing parse, and variants thereof? Should I ask that on meta? – Peter Wood Apr 17 '13 at 19:47
  • 2
    @Dave: I have "different behaviour", because I work with compilers supporting the /current/ C++ standards as good as they can. – PlasmaHH Apr 17 '13 at 19:49

2 Answers2

5

You have run into the most vexing parse. The compiler is parsing the definition of theList(std::allocator<int>()) as a function declaration. This should fix you up:

std::allocator<int> alloc;
std::list<int, std::allocator> > theList(alloc);
Pete Becker
  • 74,985
  • 8
  • 76
  • 165
  • @GManNickG - `std::list` does not have a constructor that takes an rvalue reference, so passing `move(alloc)` will call the lvalue constructor, so there's no difference. – Pete Becker Apr 17 '13 at 20:18
  • @PeteBecker: You're right, my fault. I was a bit surprised at first, but it makes sense since the allocator is almost certainly just going to be `rebind`ed. – GManNickG Apr 17 '13 at 20:20
3

It's known as the most vexing parse.

You accidentally declared a function. Adding an extra set of parenthesis will make it unambiguous.

std::list<int, std::allocator<int>> theList((std::allocator<int>()));
//                                          ^                     ^
Drew Dormann
  • 59,987
  • 13
  • 123
  • 180