0

I'm trying to implement the iostream operators as a friend function to a variadic class template.

#include <utility>
#include <iostream>

template<typename... Args>
class StudentInformation {
public:
    //friend class Student;
    using Members = std::tuple<Args...>;
    Members members;

    const size_t numArgs{ sizeof...(Args) };

    StudentInformation( Args&&... args ) : members{ std::make_tuple<Args...>( std::move( args )... ) } {}


    const StudentInformation<Args...>& operator() ( Args... args ) {
        members = std::make_tuple<Args>( std::forward<Args>( args )... );
        return *this;
    }

    const StudentInformation<Args...> operator() ( Args... args ) const {
        members = std::make_tuple<Args>( std::forward<Args>( args )... );
        return *this;
    }


    friend std::ostream& operator<< ( std::ostream& out, const StudentInformation& c );
    friend std::istream& operator>> ( std::istream& in, StudentInformation& c );
};

template<typename... T>
std::ostream& operator<<( std::ostream& out, const StudentInformation<T...>& c ) {
    const size_t numArgs = c.numArgs;
    for( size_t idx = 0; idx < numArgs; idx++ )
        out << std::get<idx>( c.members ) << " ";
    return out;
}

template<typename... T>
std::istream& operator>>( std::istream& in, StudentInformation<T...>& c ) {
    const size_t numArgs = c.numArgs;
    for( size_t idx = 0; idx < numArgs; idx++ )
        in >> std::get<idx>( c.members );
    return in;
}

And for some reason I'm still getting a linker error. Even when the overloads are defined in the header I'm still getting a linker error as if they were defined in a cpp file. Any thoughts?


Even when I try this approach: Declaring a prototype of the class first, then declaring prototypes of the overloaded operators, then declaring the class, then defining the friend overloaded operators:

#include <utility>
#include <iostream>

template<typename... Args>
class StudentInfo;

template<typename... Args>
std::ostream& operator<<( std::ostream& out, const StudentInfo<Args...>& c );

template<typename... Args>
std::istream& operator>>( std::istream& in, StudentInfo<Args...>& c );

template<typename... Args>
class StudentInfo {
public:
    //friend class Student;
    using Members = std::tuple<Args...>;
    Members members;

    const size_t numArgs{ sizeof...(Args) };

    StudentInfo( Args&&... args ) : members{ std::make_tuple<Args...>( std::move( args )... ) } {}


    const StudentInfo<Args...>& operator() ( Args... args ) {
        members = std::make_tuple<Args>( std::forward<Args>( args )... );
        return *this;
    }

    const StudentInfo<Args...> operator() ( Args... args ) const {
        members = std::make_tuple<Args>( std::forward<Args>( args )... );
        return *this;
    }


    friend std::ostream& operator<< ( std::ostream& out, const StudentInfo& c );
    friend std::istream& operator>> ( std::istream& in, StudentInfo& c );
};

template<typename... T>
std::ostream& operator<<( std::ostream& out, const StudentInfo<T...>& c ) {
    const size_t numArgs = c.numArgs;
    for( size_t idx = 0; idx < numArgs; idx++ )
        out << std::get<idx>( c.members ) << " ";
    return out;
}

template<typename... T>
std::istream& operator>>( std::istream& in, StudentInfo<T...>& c ) {
    const size_t numArgs = c.numArgs;
    for( size_t idx = 0; idx < numArgs; idx++ )
        in >> std::get<idx>( c.members );
    return in;
}

I'm still getting a linker error. I could make these a part of the class; but I don't want that behavior. I want them to be defined outside of the class. As it currently stands; everything in the class right now is public only for testing, but once this class works as intended; everything in it will be private as this class will be a friend to another class; where the owning class will have access to all of its private information. I don't know how to get around or resolve this linker error problem.

1>main.obj : error LNK2019: unresolved external symbol "class std::basic_ostream<char,struct std::char_traits<char> > & __cdecl operator<<(class std::basic_ostream<char,struct std::char_traits<char> > &,class StudentInfo<class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >,class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >,int,class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >,class std::vector<int,class std::allocator<int> > > const &)" (??6@YAAAV?$basic_ostream@DU?$char_traits@D@std@@@std@@AAV01@ABV?$StudentInfo@V?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@V12@HV12@V?$vector@HV?$allocator@H@std@@@2@@@@Z) referenced in function _main
Francis Cugler
  • 7,788
  • 2
  • 28
  • 59
  • I posted this a couple of days ago and didn't get a chance to review what I've posted. I was in a hurry as we were leaving to visit family for the weekend. I had stated that I defined the operators in a cpp file, but no I actually have them in an inline file that is included at the bottom of the header file. – Francis Cugler Apr 08 '18 at 04:05
  • I made some edits to my original question to make it more specific as to why it is not a duplicate in hopes to resolve the linker error I'm getting. – Francis Cugler Apr 08 '18 at 04:37

1 Answers1

0

It's a classic.

When you compile main() (say in "main.cpp"), you have

std::cout << studentA << '\n';

where studentA is a StudentInformation<std::string, std::string, int, std::string, std::vector<int>>.

So the compiler prepares an intermediate file that ask for (call) operator<<() for StudentInformation<std::string, std::string, int, std::string, std::vector<int>>.

That operator<<() should be implemented compiling the other cpp-file, say "operators.cpp".

But compiling "operator.cpp", the compiler doesn't know that an operator<<() for StudentInformation<std::string, std::string, int, std::string, std::vector<int>> is needed. So doesn't implement it.

The linker receive the intermediate files produced compiling "main.cpp" and "operators.cpp"; in the first one finds a call to operator<<() for StudentInformation<std::string, std::string, int, std::string, std::vector<int>> but doesn't find the implementation: linker error.

That's the reason because it's better define template classes (and corresponding template friend operators) in the header, not in cpp file.

Bonus error.

This code in operator<<()

for( size_t idx = 0; idx < numArgs; idx++ )
    out << std::get<idx>( c.members) << " ";

and this code in operator>>()

for( size_t idx = 0; idx < numArgs; idx++ )
    in >> std::get<idx>( c.members);

is obviously wrong because std::get() needs, for the template parameter, a compile time know value and idx, clearly, isn't know at compile time.

max66
  • 65,235
  • 10
  • 71
  • 111
  • thank you for the insight. I think I've come across this before; but couldn't seem to find the solution to it in my projects nor here. – Francis Cugler Apr 06 '18 at 18:15
  • I posted the code a few days ago. I was in rush for we were leaving to visit family this weekend so I didn't get the chance to review my posted question. I had stated that I had implemented the operators in a cpp file but that was actually wrong. I have them in an `*.inl` file and I'm still getting the same linker error. The `*.inl` file is included at the bottom of the header file after the class template declaration. Any thoughts? – Francis Cugler Apr 08 '18 at 04:12
  • Not completely clear, for me. I suggest to prepare a complete example (minimal as possible) that reproduce the error. Where for "complete" I mean with *all* files and their names, all includes, a `main()`, command line for compiling and command line for linking. This question is closed as duplicate so I suggest to open another one. – max66 Apr 08 '18 at 11:27