Передача параметра структуры в шаблонный метод c ++ - PullRequest
0 голосов
/ 22 мая 2018

У меня проблема с передачей параметра в шаблон класса

struct Car {
    int id;
    char *model;
    int date;
    int cost;
};

template <class T>
class Set
{
private:
    int *a;
    int _size;
    map<int, T> data;
public:

    //some methods before

    void insert(T x)
    {

        int num;
        if (std::is_same<T, Car>::value)
            num = x.id;
        if (num >= 0 && num < (_size << 5))
            throw "Element is out of set size!";
        a[num / 32] = a[num / 32] | (1 << (num % 32));
        if (std::is_same<T, Car>::value)
            data.insert(make_pair(num, x));
    }
};

Мой метод insert должен принимать оба типа int и Car struct.Но Visual Studio говорит, что у меня ошибка компиляции на num = x.id;, что x должен быть классом, структурой или объединением.Я мог бы передать указатели на эту функцию, но я не смогу передавать целые числа, такие как class.insert(5).Как я могу решить это или как я могу заставить метод принимать оба указателя на структуру и регулярные переменные, не задавая другой тип одному из типов?

Ответы [ 3 ]

0 голосов
/ 22 мая 2018

Чтобы эта строка была даже скомпилирована, вы должны будете использовать if constexpr:

        if constexpr(std::is_same<T, Car>::value) num = x.id;

Но учтите, что с вашего текущего кода, num остается неинициализированным (и используется в следующей строкетем не менее).

Наиболее разумным способом, если вы хотите иметь возможность insert аргументов типов T, Car и int, является, вероятно, распределение функциональности между тремя перегрузками:

void insert(T x);
void insert(Car x);
void insert(int x);

И дополнительно специализировать шаблон для обоих типов Car и int, иначе он не будет компилироваться.

Или вы имели в виду, что Set должен действительно приниматьэлементы всех типов?Тогда это гораздо сложнее.Прежде всего, сам метод insert должен быть шаблонным:

template<typename T> void insert(T x);

, тогда как сам класс, скорее всего, не должен:

class Set;

(Тогда в шаблонном insert вы можете использовать все, что было сказано о if constexpr в этой теме.)

Далее базовый контейнер должен принимать значения всех типов, что означает, что вам нужно будет использоватьчто-то вроде map<int, std::any> (Кстати, почему map? Сортировка по идентификаторам, вероятно, не очень согласована, если вы храните автомобили, смешанные с целыми числами. Здесь вы можете использовать unordered_map или даже unordered_set со своим собственным многогранным хэшемfunction.)

И последнее, но не менее важное: потребуется некоторое время, чтобы создать минимально согласованную схему для фактического извлечения сохраненных значений.Основная проблема заключается в том, что вам придется хранить их с типом стирания, поэтому вам нужно каким-то образом вспомнить фактический тип хранимого значения - не совсем нелегким способом.

Это так?что ты пытаешься построить?

0 голосов
/ 22 мая 2018

Рассмотрим этот упрощенный пример:

template <typename T> 
struct foo { 
    T t;
    void bar() {
        if (std::is_same<T,Car>) { std::cout << t.id; }
    }
};

Теперь вы должны помнить, что создание экземпляра шаблона происходит во время компиляции, т. Е. Для foo<Car> компилятор пока создает

 struct foo<Car> {  
     Car t;
     void bar() {
          if (true) { std::cout << t.id; }
     }
 };

это хорошо, но когда вы создаете экземпляр того же шаблона для int, вы получаете

 struct foo<int> {  
     int t;
     void bar() {
          if (false) { std::cout << t.id; }
     }
 };

, проблема в том, что, хотя условие всегда ложно, код все равно должен компилироваться.int не имеет id, следовательно, ваш код не компилируется.

Есть несколько способов решить вашу проблему.Вы можете использовать SFINAE (что может немного сбивать с толку, если вы не привыкли к нему, как я;), if constexpr (что исключает необходимость использования не взятых ветвей) или просто явно указать нужные вам специализации.

0 голосов
/ 22 мая 2018

Да, это проблема, с которой многие люди борются.Давайте посмотрим на ваш метод шаблона snipped:

void insert(T x)
{
    int num;
    if (std::is_same<T, Car>::value)
        num = x.id;
    //...

Проблема здесь в том, что оператор if семантически оценивается во время выполнения (даже если компилятор может оптимизировать ветвь, потому что он будет знать значение во время компиляции)и, таким образом, компилятор должен «притворяться», чтобы генерировать код для обеих ветвей.Кстати, когда ветвь не берется, ваш num остается унифицированным, что является неопределенным поведением.

В вашем случае это означает, что даже если T не Car, семантически код по-прежнему нуждаетсягенерироваться для num = x.id.Очевидно, что int.id не является допустимым оператором.

Для решения этой проблемы с C ++ 17 вы должны использовать if constexpr:

if constexpr (std::is_same<T, Car>::value) //...

Constexpr if s гарантированно будетоценивается во время компиляции, и код, принадлежащий не занятой ветви, не будет оцениваться.

В мире, предшествующем C ++ 17, вы обычно можете использовать либо диспетчеризацию тегов, либо SFINAE.Я всегда предпочитаю отправку тегов, так как думаю, что это намного проще, чем SFINAE.Диспетчеризация тегов основывается на использовании перегрузки функции, и в вашем случае это будет что-то вроде (поскольку неясно, что будет делать ваш код при передаче int, я буду делать вид, что вам нужно установить num для переданного значения int:

int get_id(const Car& car) {
    return car.id;
}

int get_id(int v) {
    return v;
}

void insert(const T& t) {
    int num = get_id(t);
    // ...

Обратите внимание, что в данном конкретном случае это даже не отправка тегов, так как случай очень прост.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...