Метод шаблона С++: Почему Node <int> в порядке, но не Node <float>?

Я редко использую шаблоны. Я не знаю, почему я вижу ошибку сборки в приведенном ниже коде для метода push Node<float>

Ошибка сборки: нет соответствующей функции для вызова push.

Node<int>* Метод push хорош хотя.

Node<float>* head1 = NULL;
push(&head1, 1);

template <typename T>
struct Node {
    T data;
    Node* next;
};

template <typename T>
void push(Node<T>** head, T data) {
    Node<T>* tmp = *head;
    Node<T>* newNode = NULL; //createNode(data);

    if (tmp == NULL) {
        *head = newNode;
    }
    else {
        while (tmp->next)
            tmp=tmp->next;

        tmp->next = newNode;
    }
}

int main(int argc, const char * argv[]) {
    Node<float>* head1 = NULL;
    push(&head1, 1);

    Node<int>* head = NULL;
    push(&head, 1);

    return 0;
}

Ответ 1

Для push(&head1, 1); тип &head1 - Node<float>**, а тип 1 - int, тогда тип вывода для параметра шаблона T будет терпеть неудачу с конфликтующими типами (float vs. int).

Вы можете сопоставить типы:

push(&head1, 1.0f);

или явно указать аргумент шаблона float, а 1 будет переведен на float.

push<float>(&head1, 1);

Ответ 2

В качестве альтернативы вы можете сделать второй аргумент не выводимым:

template <typename T> struct non_deducible
{
    using type = T;
};
template <typename T> using non_deducible_t = non_deducible<T>::type

template <typename T>
void push(Node<T>** head, non_deducible_t<T> data)

Ответ 3

Проблема заключается в том, что в случае с плавающей точкой T data выводится тип float, но вы передаете ему целочисленное значение:

template <typename T>
void push(Node<T>** head, T data)
                        --^


push(&head1, 1);
   +--^      ^-- int
   ^ Node<float>*

Если вы измените это на

push(&head1, 1.0f);

он работает: http://ideone.com/hxaDZ5

Ответ 4

Компилятор С++ обрабатывает все аргументы функции симметрично. Каждый имеет одинаковое значение.

template <typename T>
void push(Node<T>** head, T data)

здесь мы имеем два аргумента. Когда мы вызываем push, не передавая явно его T, мы выводим T из аргументов.

Если вы передадите ему указатель на указатель на Node<float> и int, компилятор запутается. Является T a float, или int?

Пока вы могли его решить, и мы могли бы изменить С++ для его решения, результат может быть легко удивителен. Поэтому вместо этого С++ говорит: "Выведенные типы несовместимы".

Мы можем исправить это несколькими способами.

  • Вы можете передать float для data. push(&head, 0.1f) или push(&head, float(1)) или что-то еще.

  • Вы можете явно передать тип T: push<float>(&head, 1.0).

  • Вы можете заблокировать вывод для второго аргумента. Это означает, что T выводится из первого аргумента и никогда не из второго:

    template<class T>struct block {using type=T;};
    template<class T>using block_deduction = typename block<T>::type;
    
    template <typename T>
    void push(Node<T>** head, block_deduction<T> data)
    
  • Вы можете понять, что нам не важно, что такое второй тип аргументов, и оставьте его свободным:

    template <typename T, typename U>
    void push(Node<T>** head, U data)
    
  • Вы можете заменить push на более современный emplace, который позволяет передать конструктивные аргументы для T вместо T напрямую. Поскольку конструкторы копирования означают, что переменная типа T действительна, обычно это замена замены:

    template <typename T, typename...Args
    void emplace(Node<T>** head, Args&&...args) {
      Node<T>* tmp = *head;
      Node<T>* newNode = new Node<T>(std::forward<Args>(args)...);
      // ...
    

    Это версия с турбонаддувом. Он позволяет создавать Node<non_movable_type>, и это может привести к повышению эффективности в других случаях, таких как типы, которые дешевы для построения, но дорогостоящие для перемещения.

Все вышесказанное - разумные решения, но я нахожу первые два плохих, так как они оставляют ваш интерфейс хрупким.

Ответ 5

вы можете попробовать:

Node<float>* head1 = NULL;
push(&head1, float(1));

Ответ 6

Другой альтернативой является использование второго параметра typename в шаблоне push:

template <typename T, typename U>
void push(Node<T>** head, U data) {
    ...
}

Он выводит T для float и U в int, и он работает, потому что он применяет преобразование с плавающим целым числом.

Также вы можете добавить std::is_assignable<T,U> к шаблону, но довольно больно его использовать.