Lavanti
Lavanti

Reputation: 151

C++ template method: Why Node<int> is fine, but not Node<float>?

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;
}

Upvotes: 15

Views: 1579

Answers (6)

Zhen
Zhen

Reputation: 4283

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.

Upvotes: 0

kfsone
kfsone

Reputation: 24269

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

Upvotes: 1

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275740

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.

Upvotes: 1

Jarod42
Jarod42

Reputation: 217810

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)

Upvotes: 7

songyuanyao
songyuanyao

Reputation: 172964

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

Upvotes: 21

everettjf
everettjf

Reputation: 126

you could try:

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

Upvotes: 0

Related Questions