Why LNK1120 & LNK2019 appears in case of template and friend function

旧时模样 提交于 2019-12-11 01:44:00

问题


I have compiled the first version of code in Turbo-C and it compiles without any error. But when I compile this in Visual Studio or plain g++ from CommandLine, I get errors mentioned down in the post.

I did searched over internet and read some of the StackOverflow questions & MSDN LNK1120 problem docs. I found a way to fix the below code. However, didn't got the reason clearly. How can just a definition gets rid of that error. Syntactically the error prone code also look fine.

1) Error    2   error LNK1120: 1 unresolved externals   

2) Error    1   error LNK2019: unresolved external symbol "void __cdecl  

totalIncome(class Husband<int> &,class Wife<int> &)" (?totalIncome@@YAXAAV?
$Husband@H@@AAV?$Wife@H@@@Z) referenced in function _main   

Note: Please watch out for arrows in program. I've explicitly put them. So, might not go through the complete program.

Error Prone Code:

#include <iostream>

using namespace std;

template<typename T> class Wife;           // Forward declaration of template    

template<typename T>
        class Husband{
        friend void totalIncome(Husband<T> &hobj, Wife<T> &wobj);

        public:
            Husband() = default;
            Husband(T new_salary): salary{new_salary} {}

        private:
            T salary;
        };

template<typename T>
        class Wife{
        friend void totalIncome(Husband<T> &hobj, Wife<T> &wobj);

        public:
        Wife() = default;
        Wife(T new_salary): salary{new_salary} {}

        private:
            T salary;
        };

template<typename T> void totalIncome(Husband<T> &hobj, Wife<T> &wobj)   __
{                                                                         |
    cout << "Total Income of Husband & Wife: ";                           |
    cout << hobj.salary + wobj.salary;                                    |
}                                                                         |
                                                                        ---
int main()
{
    Husband<int> h(40000);
    Wife<int> w(90000);

    totalIncome(h, w);

    return 0;
}

but in the below case by just moving the definition up in the class, it runs perfectly fine. Why whats the reason?

Fixed Code:

#include <iostream>

using namespace std;

template<typename T> class Wife;           // Forward declaration of template    

template<typename T>
        class Husband{
        friend void totalIncome(Husband<T> &hobj, Wife<T> &wobj);

        public:
            Husband() = default;
            Husband(T new_salary): salary{new_salary} {}

        private:
            T salary;
        };

template<typename T>
        class Wife{
        friend void totalIncome(Husband<T> &hobj, Wife<T> &wobj)  __
        {                                                           |              
           cout << "Total Income of Husband & Wife: ";              | -- definition moved up here          
           cout << hobj.salary + wobj.salary;                       |             
        }                                                         __|

        public:
        Wife() = default;
        Wife(T new_salary): salary{new_salary} {}

        private:
            T salary;
        };

/*
template<typename T> void totalIncome(Husband<T> &hobj, Wife<T> &wobj)   __
{                                                                         |
    cout << "Total Income of Husband & Wife: ";                           |
    cout << hobj.salary + wobj.salary;                                    |
}                                                                         |
                                                                        ---
*/

int main()
{
    Husband<int> h(40000);
    Wife<int> w(90000);

    totalIncome(h, w);

    return 0;
}

回答1:


First, let's cut this down to a minimal example that reproduces the issue:

template<typename T>
        class Husband{
        friend void totalIncome(Husband<T> &);
        };

template<typename T> void totalIncome(Husband<T> &hobj)
{}

int main()
{
    Husband<int> h;
    totalIncome(h);
}

This leads to a similar linker error. For example, using clang++:

/tmp/main-fb41c4.o: In function `main':
main.cpp:(.text+0xd): undefined reference to `totalIncome(Husband&)'

The underlying issue is the same as the one in Overloaded arithmetic operators on a template causing an unresolved external error and in Strange behavior of templated operator<<. Hence, I'll base my answer here on my answer from the second question.


Friend function declarations

In the class template Husband, there is a friend-declaration of the function totalIncome:

friend void totalIncome(Husband<T> &);

A friend function declaration looks up the name of the declared function (here: totalIncome) in the surrounding scopes up to and including the innermost enclosing namespace. If a declaration of that name is found, the declared entity is befriended:

class Husband;
void totalIncome(Husband&);             // (A)

class Husband{
    friend void totalIncome(Husband&);  // befriends (A)
};

If no declaration of the name is found, a new function is declared in the innermost enclosing namespace:

class Husband{
    friend void totalIncome(Husband&);  // declares and befriends ::totalIncome
};

void totalIncome(Husband&);             // friend of class Husband

If the function is only declared via the friend function declaration (and there is no later declaration in the enclosing namespace), the function can only be found via Argument-Dependent Lookup.


Befriending a function in a class template

The issue in the OP's code is that there is a class template involved:

template<typename T>
        class Husband{
        friend void totalIncome(Husband<T> &);
        };

The rules stay the same: No declaration of totalIncome is found. Since the signature of the friend declaration depends on the template parameter of the class template, every instantiation of Husband will introduce a new function in the enclosing namespace. (If the friend declaration did not depend on a template parameter, you'd get a redeclaration with every instantiation of Husband.) For example, if you instantiate Husband<int> and Husband<double>, you'll get two functions in the global namespace:

void totalIncome(Husband<int> &);
void totalIncome(Husband<double> &);

Note that these are two unrelated functions, much like:

void totalIncome(int &);
void totalIncome(double &);

Overloading a function with a function template

You can overload an "ordinary" function with a function template:

void totalIncome(Husband<int> &);    // (A)

template<typename T>
    void totalIncome(Husband<T> &);  // (B)

When calling the function via Husband<int> x; totalIncome(x);, the function template will produce an instantiation:

void totalIncome<int>(Husband<int> &); // (C), instantiated from (B)

And your overload set consists of two functions:

void totalIncome(Husband<int> &);       // (A)
void totalIncome<int>(Husband<int> &);  // (C)

All things equal, overload resolution will prefer the non-template function (A) over the function template specialization (C).

And this is what happens in the OP's code as well: There is a non-template function introduced by instantiating the class template Husband, and an unrelated function template. Overload resolution chooses the non-template function, and the linker complains that it doesn't have a definition.


Various solutions

The simplest solution is to define the friend function inside the class definition:

template<typename T>
        class Husband{
        friend void totalIncome(Husband<T> &){
            // code here
        }
        };

A solution using forward-declarations:

template<typename T>
        class Husband;

template<typename T>
        void totalIncome(Husband<T> &);

template<typename T>
        class Husband{
        friend void totalIncome(Husband<T> &);
        };

template<typename T> void totalIncome(Husband<T> &hobj)
{}

Here, the compiler can find the forward-declared function template and befriend a specialization of it, instead of declaring a new function. It might be worthwhile explicitly adding the template arguments:

template<typename T>
        class Husband{
        friend void totalIncome<T>(Husband<T> &);
        };

Since this enforces that there is a prior declaration of the function template totalIncome.


A solution befriending the whole function template:

template<typename T>
        class Husband{
        template<typename U>
                friend void totalIncome(Husband<U> &);
        };

template<typename T> void totalIncome(Husband<T> &hobj)
{}

In this solution, the friend-declaration declares a function template, and the later declaration at namespace scope following the class' definition redeclares and defines this function template. All specializations of the function template will be befriended by all instantiations of Husband.



来源:https://stackoverflow.com/questions/29055696/why-lnk1120-lnk2019-appears-in-case-of-template-and-friend-function

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!