Compiler error when using map[key] = value syntax

First, my environment:
- FreeBSD 9.2-RELEASE amd64
- clang 3.3 from base
- libc++ and libcxxrt compiled and installed from base (WITH_LIBCPLUSPLUS=yes)

Now, my problem: I am working through a C++11 course and am currently learning about std::map. I created a basic test class called Person, initialized some Person objects on the stack, and am attempting to copy these Person objects into a std::map<int, Person> using the map[key] = value syntax. Code from test.cpp:

Code:
#include <map>
#include <string>

using namespace std;

class Person
{
  private:
    string firstname;
    string lastname;
    int age;

  public:
    Person(string fn, string ln, int a) : firstname(fn), lastname(ln), age(a) {}
    ~Person() {}
    string GetName() const { return firstname + " " + lastname; }
    int GetAge() const { return age; }
};

int main()
{
  Person p1("John", "Doe", 34);
  Person p2("Jane", "Doe", 21);

  map<int, Person> people;
  pair<int, Person> pp(p1.GetAge(), p1);
  people.insert(pp); // this works

  people[p2.GetAge()] = p2; // compiler error, see below

  return 0;
}

Error message:

Code:
In file included from test.cpp:1:
In file included from /usr/include/c++/v1/map:371:
In file included from /usr/include/c++/v1/__tree:16:
/usr/include/c++/v1/memory:1689:31: error: no matching constructor for initialization of 'Person'
            ::new((void*)__p) _Up(_VSTD::forward<_Args>(__args)...);
                              ^
/usr/include/c++/v1/memory:1616:18: note: in instantiation of function template specialization
      'std::__1::allocator<std::__1::__tree_node<std::__1::map<int, Person, std::__1::less<int>,
      std::__1::allocator<std::__1::pair<const int, Person> > >::__value_type, void *>
      >::construct<Person, >' requested here
            {__a.construct(__p, _VSTD::forward<_Args>(__args)...);}
                 ^
/usr/include/c++/v1/memory:1497:14: note: in instantiation of function template specialization
      'std::__1::allocator_traits<std::__1::allocator<std::__1::__tree_node<std::__1::map<int,
      Person, std::__1::less<int>, std::__1::allocator<std::__1::pair<const int, Person> >
      >::__value_type, void *> > >::__construct<Person, >' requested here
            {__construct(__has_construct<allocator_type, pointer, _Args...>(),
             ^
/usr/include/c++/v1/map:1153:20: note: in instantiation of function template specialization
      'std::__1::allocator_traits<std::__1::allocator<std::__1::__tree_node<std::__1::map<int,
      Person, std::__1::less<int>, std::__1::allocator<std::__1::pair<const int, Person> >
      >::__value_type, void *> > >::construct<Person, >' requested here
    __node_traits::construct(__na, _VSTD::addressof(__h->__value_.__cc.second));
                   ^
/usr/include/c++/v1/map:1219:29: note: in instantiation of member function 'std::__1::map<int,
      Person, std::__1::less<int>, std::__1::allocator<std::__1::pair<const int, Person> >
      >::__construct_node_with_key' requested here
        __node_holder __h = __construct_node_with_key(_VSTD::move(__k));
                            ^
test.cpp:29:11: note: in instantiation of member function 'std::__1::map<int, Person,
      std::__1::less<int>, std::__1::allocator<std::__1::pair<const int, Person> > >::operator[]'
      requested here
    people[p2.GetAge()] = p2; // compiler error, see below
          ^
test.cpp:14:9: note: candidate constructor not viable: requires 3 arguments, but 0 were provided
        Person(string fn, string ln, int a) : firstname(fn), lastname(ln), age(a) {}
        ^
test.cpp:6:7: note: candidate constructor (the implicit copy constructor) not viable: requires 1
      argument, but 0 were provided
class Person
      ^

The course material uses Microsoft Visual Studio 2010 and the above code compiles under that without error. Any help would be greatly appreciated.
 
Try providing the Person class an explicit default constructor. It may be that the Microsoft compiler provides one automatically but clang does not.
 
You're right. clang wasn't providing a default constructor for the Person class, only a default copy constructor. This is because I explicitly provided a constructor of my own. The above code now compiles and runs after having added the following public inline definition to Person:

Code:
Person() {}

Thank you very much, kpa. Now I know to be more wary of implementation differences while going through this Microsoft-centric course. But at least the examples don't use any nonstandard MS extensions.
 
My guess is that you only need a default constructor when using std::map::operator[]() since this operator inserts a default-constructed element when a given key is not found in the map. If I recall correctly, since std::map is templated, this operator will not even be instantiated unless used.

If you use std::map::insert instead, your previous code will probably work.

See these references:
http://www.cplusplus.com/reference/map/map/operator[]/
http://www.cplusplus.com/reference/map/map/insert/

I don't think this is a real implementation difference. Even SGI's STL documentation mentioned that operator[]() is a convenience that really inserts a default-constructed element in the absence of the key. So you should expect to need a default constructor when using that operator.

But here is one real example of an STL implementation difference:
http://www.cplusplus.com/reference/map/map/erase/
http://msdn.microsoft.com/en-us/library/z2f3cb7h.aspx

Notice Microsoft's version is not standard. Although, I think Microsoft's version is more reasonable.

More likely, any difference you see between Visual Studio and clang/GCC is Visual Studio compiling code that shouldn't compile in the first place. Visual Studio should not be used in programming courses in my opinion. Its treatment of templates, in particular, is often wrong (violates the standard). But it does produce fast executables!
 
Back
Top