-1

I'm a C++ beginner trying to implement Course class which has a stl unordered_map as member variable to store the students' grades.

#include <iostream>
using namespace std;

class Course{

    string course_name;
    Supervisor PI;
    Teacher TA;
    unordered_map<Student, int> grades;

public:

    Course(string name, 
    void get_student_grade(Student person){
        return grades. ;
    }
    void printGrades(){
        return ;
    }
};

class Student{

string name;

public:

    Student(string namee):name(namee){}
};

int main(){

    Course calculus{"calculus", Student Matt("Matt"), Student James("James"), 
        Student Michelle("Michelle"), Student Ashley("Ashley"), Student Karen("Karen")};

    calculus.printGrades(); 
}

I am trying to create a course constructor that takes the course string and arbitrary number of student object arguments and store it in unordered_map.

So far Google has taught me there is something called variadic template to enable arbitrary number of arguments, but I don't seem to get how to apply that in my case. Also, would it be possible to store Student object as key for the unordered_map? By this I mean when I do sth like

return grades[James];

, the hash table would search for string James and then return the value.

Thanks for helping out!

DHH
  • 97
  • 6
  • Looks like you want an [std::initializer_list](https://en.cppreference.com/w/cpp/utility/initializer_list). Or a template-sized array, but that's a bit more complicated. – Nelfeal Jan 06 '19 at 14:43
  • You would need to provide hashing function for `Student` type to use it as a key in `std::unordered_map`. It would be probably better to move grades into `Student` object and use simple `std::vector` – Yksisarvinen Jan 06 '19 at 14:46
  • @Nelfeal How exactly should I use initializer_list to enable arbitrary arguments on the Course constructor? – DHH Jan 06 '19 at 14:50
  • Are you missing some code after your `Course` constructor definition? You have a wealth of syntax errors here. – Lightness Races in Orbit Jan 06 '19 at 15:02
  • Variadic templates are probably rather complicated for a beginner - if you first learn how to pass function arguments (you're doing it wrong atm) then progress to more advanced topics later you'll probably be better off. Which book are you using? – Lightness Races in Orbit Jan 06 '19 at 15:03
  • @DHH I'm guessing you mean "arbitrary number of arguments (of type `Student`)". If so, that would be something like `Course(std::string name, std::initializer_list students)`. But you will need a additional pair of braces when constructing a `Course`. It's kind of like constructing an array of `Student`s first, and passing it to `Course`'s constructor as a single array argument. – Nelfeal Jan 06 '19 at 15:25
  • The shown example has multiple problems. Obvious typos: "`return grades.;`". Missing `#include` of `unordered_map`. Unordered maps require a hash function, which is completely unspecified (`Student` is not hashable). Furthermore, an unordered map is an associative container, and the shown attempt to initialize ot lists the keys only, and the values are missing. Missing definitions of several classes. Please clean up your sample code so that the ***only*** issue is the unspecified initialization of the unordered map. – Sam Varshavchik Jan 06 '19 at 15:37
  • @Lightness Races in Orbits This is just my scratch code not from any book, and it is unfinished. Is my func arg wrong cuz I didn’t pass by reference? (i.e. void get_student_grade(Student &person); )Would u recommend me some great books to seriously up my cpp? – DHH Jan 06 '19 at 15:38
  • You should be learning from one of these: https://stackoverflow.com/questions/388242/the-definitive-c-book-guide-and-list – Lightness Races in Orbit Jan 06 '19 at 15:46
  • Please finish your code before using it to ask about further things. There are obvious omissions/problems with the posted code. It looks like you removed some lines during copy/paste? – Lightness Races in Orbit Jan 06 '19 at 15:46

1 Answers1

1

You should use variadic templates only when you need no homogeneous lists (or for some other rare cases when the types must be moved). In your case std::initializer_list is sufficient.

Here are both ways (notice the missing curly brackets when creating the course). Using both implementations in a single class is a bad idea, this is just for demonstration.

class Course {
  std::string course_name;
  std::unordered_map<Student, int> grades;

 public:
  Course(std::string name, std::initializer_list<Student> students) {
    for (const auto& s : students) grades[s] = 0;
  }

  template <typename... Students>
  Course(std::string, Students&&... students) {
    ((grades[std::forward<Students>(students)] = 0), ...);
  }
};

int main() { 
  Course initializer{"calculus", {Student{"Matt"}, Student{"James"}}}; 
  Course variadic{"calculus", Student{"Matt"}, Student{"James"}};
}

You have to provide a comparison operator and a hash function in order to use Student as the key. There are several ways to provide the hash function, this version specializes the STL implementation.

class Student {
 public:
  Student(std::string namee) : name(namee) {}
  bool operator==(const Student& other) const { return name == other.name; }
  std::string name;
};

namespace std {
template <>
struct hash<Student> {
  size_t operator()(const Student& s) const {
    return std::hash<std::string>()(s.name);
  }
};
}  // namespace std

Note that I made name public for convenience.

local-ninja
  • 1,198
  • 4
  • 11