Aug 16, 2017

C++: Приведение Enum структур к общему виду в шаблонах

Задача - создать шаблонную функцию, в которой в зависимости от типа перечисления выбирается нужный индекс в контейнере vector. Попробуем решить задачу в лоб, создадим 3 перечисления и выберем нужный индекс в шаблоне.

Использование enum в шаблоне напрямую
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
#include <vector>
#include <iostream> // cout

struct PEOPLE {
  enum {
    PEOPLE_ID,    // 0
    FIRSTNAME,    // 1
    LASTNAME      // 2
  };
};

struct STUDENT {
  enum {
    LASTNAME,     // 0
    FIRSTNAME,    // 1
    SALARY,       // 2
    _ID,          // 3
  };
};

struct EMPLOYE {
  enum {
    LASTNAME,     // 0
    _ID,          // 1
    FIRSTNAME,    // 2
    SALARY        // 3
  };
};

typedef std::vector<std::string> t_item;

template <class T>
void printHuman(t_item item, T _type)
{
  std::string salary   = "0.00 $";
  std::string human_id = "-1";

  const bool isStudent = std::is_same<T, STUDENT>::value;
  const bool isEmploye = std::is_same<T, EMPLOYE>::value;

  // PEOPLE
  if (std::is_same<T, PEOPLE>::value) {
    human_id = item.at(PEOPLE::PEOPLE_ID);
  }
  // STUDENT || EMPLOYE
  else if (isStudent || isEmploye) {
    // Выбираем поле в зависимости от типа структуры
    salary   = item.at(T::SALARY);
    human_id = item.at(T::_ID);
  }

  // Отобажаем на экране
  std::cout << human_id
            << "\t\t"
            << item.at(T::FIRSTNAME)
            << "\t\t"
            << item.at(T::LASTNAME)
            << "\t\t"
            << salary
            << std::endl;
}

int main()
{
  // Создаем людей разных типов
  t_item vasya{"1", "Vasya", "Vasnetsov"};                    // PEOPLE
  t_item petya{"Иванов", "Petr", "10.00 $", "13"};            // STUDENT
  t_item bjarne{"Бьёрн", "66", "Straustrup", "100 000.00 $"}; // EMPLOYE

  // Выводим информацию о людях
  printHuman(vasya,  PEOPLE());
  printHuman(petya,  STUDENT());
  printHuman(bjarne, EMPLOYE());

  return 0;
}

Скомпилируем пример с добавлением C++11.

Попытка компиляции enum в шаблоне
$ g++ -std=c++11 main.cpp -o enum-example
main-bad.cpp: In instantiation of ‘void printHuman(t_item, T) [with T = PEOPLE; t_item = std::vector<std::__cxx11::basic_string<char> >]’:
main-bad.cpp:78:30:   required from here
main-bad.cpp:55:14: error: ‘SALARY’ is not a member of ‘PEOPLE’
     salary   = item.at(T::SALARY);
              ^
main-bad.cpp:56:14: error: ‘_ID’ is not a member of ‘PEOPLE’
     human_id = item.at(T::_ID);
              ^
Makefile:7: recipe for target 'bad' failed
make: *** [bad] Error 1

Получаем ошибку, т.к. подстановка типа перечисления PEOPLE не имеет атрибутов _ID и SALARY.

Что бы обойти эту проблему выведем тип с помощью std::conditional.

Использование enum в шаблоне через выведение типа енума
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
void printHuman(t_item item, T _type)
{
  std::string salary   = "0.00 $";
  std::string human_id = "-1";

  const bool isStudent = std::is_same<T, STUDENT>::value;
  const bool isEmploye = std::is_same<T, EMPLOYE>::value;

  // PEOPLE
  if (std::is_same<T, PEOPLE>::value) {
    human_id = item.at(PEOPLE::PEOPLE_ID);
  }
  // STUDENT || EMPLOYE
  else if (isStudent || isEmploye) {
    // Выбираем поле в зависимости от типа структуры
    typedef std::conditional<isStudent, STUDENT, EMPLOYE> cond;
    salary   = item.at(cond::type::SALARY);
    human_id = item.at(cond::type::_ID);
  }

  // Отобажаем на экране
  std::cout << human_id
            << "\t\t"
            << item.at(T::FIRSTNAME)
            << "\t\t"
            << item.at(T::LASTNAME)
            << "\t\t"
            << salary
            << std::endl;
}

В результате программа скомпилируется без ошибок. Это происходит потому, что conditional реализует свой шаблон на базе только двух нужных нам enum’ов (STUDENT, EMPLOY).

Компиляция и запуск
$ g++ -std=c++11 main.cpp -o enum-example
$ ./enum-example
1               Vasya           Vasnetsov               0.00 $
13              Petr            Иванов          10.00 $
66              Straustrup              Бьёрн           100 000.00 $

Comments

comments powered by Disqus