15

I rarely use templates. I don't know why I see a build error in the below code for the push method of Node<float>

Build Error is: No matching function to call push.

Node<int>* push method is fine though.

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;
}
songyuanyao
  • 169,198
  • 16
  • 310
  • 405
Lavanti
  • 151
  • 6

6 Answers6

21

For push(&head1, 1);, the type of &head1 is Node<float>**, and type of 1 is int, then type deduction for template parameter T will fail with conflicting types (float vs. int).

You could make the types match:

push(&head1, 1.0f);

or explicitly specify the template argument by float, and 1 will be casted to float.

push<float>(&head1, 1);
songyuanyao
  • 169,198
  • 16
  • 310
  • 405
  • Couldn't you also make the data parameter have its own separate typename? It would then potentially fail deeper in the stack, when assigning the node data member to the argument, but in this case it should work fine. – MooseBoys May 18 '16 at 06:17
  • @MooseBoys Yes it might work. But without more details about how `data` being used in `push()`, we can't confirm. – songyuanyao May 18 '16 at 06:22
7

As alternative, you may do the second argument non deducible:

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)
Jarod42
  • 203,559
  • 14
  • 181
  • 302
1

The problem is that in the float case T data is deduced to be of type float, but you pass it an integer value:

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


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

If you change this to

push(&head1, 1.0f);

it works: http://ideone.com/hxaDZ5

kfsone
  • 23,617
  • 2
  • 42
  • 74
1

The C++ compiler treats all function arguments symmetrically. Every one is equally important.

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

here we have two arguments. When we call push without explicitly passing it T, we deduce T from the arguments.

If you pass it a pointer to a pointer to a Node<float> and an int, the compiler gets confused. Is T a float, or an int?

While you could solve it, and we could modify C++ to solve it, the result could easily be surprising. So instead, C++ says "types deduced are not consistent".

We can fix this in a number of ways.

  • You could pass a float for data. push(&head, 0.1f) or push(&head, float(1)) or whatever.

  • You can pass the type T explicitly: push<float>(&head, 1.0).

  • You can block deduction for the 2nd argument. This means that T gets deduced from the first argument, and never from the 2nd:

    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)
    
  • You can realize we don't care what the second arguments type is, and leave it free:

    template <typename T, typename U>
    void push(Node<T>** head, U data)
    
  • You can replace push with a more modern emplace that has you pass construction arguments for your T instead of a T directly. As copy constructors mean that a variable of type T is valid, this is a drop-in replacement usually:

    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)...);
      // ...
    

    This is the turbo-charged version. It lets you create Node<non_movable_type>, and it can lead to efficiencies in other cases, such as types that are cheap to construct but expensive to move around.

All of the above are reasonable solutions, but I find the first two poor, as they leave your interface brittle.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
0

you could try:

Node<float>* head1 = NULL;
push(&head1, float(1));
everettjf
  • 126
  • 9
0

Another alternative is to use a second typename parameter on the push template:

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

It deduce T to float and U to int and it works because the it applies floating-integer conversion.

Also you can add std::is_assignable<T,U> to the template, but is quite a pain to use it.

Zhen
  • 4,171
  • 5
  • 38
  • 57