0

Short introduction

I am currently working on a dependency injection example written with VC++. My current setup is based on writer (my base class), some testing classes (I'll just upload one of them -> writer2) and my holy grail aka the generic class (genericWriter), which should do the whole dependency injection magic.

But like every time I face some cpp code, the linker does not play in my cards and jumps at me with some... nice... looking... error messages:

The linking error message

PlusPlus_Example.obj : error LNK2001: unresolved external symbol "public: virtual void __thiscall genericWriter<class writer2>::print_whatever(class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >)const " (?print_whatever@?$genericWriter@Vwriter2@@@@UBEXV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@@Z)

My code:

writer.h

// writer.h
#ifndef WRITER_H
#define WRITER_H

#include <string>

#include <boost/serialization/assume_abstract.hpp>
#include <boost/serialization/export.hpp>
#include <boost/serialization/nvp.hpp>

class writer
{
public:
    writer::writer(void){}
    virtual ~writer() {};

    virtual void print_whatever(const std::string s) const = 0;

private:
    friend class boost::serialization::access;

    template<class Archive>
    void serialize(Archive & ar, const unsigned int version) { }
};

BOOST_SERIALIZATION_ASSUME_ABSTRACT(writer)

#endif

writer2.h

// writer.h
#ifndef WRITER2_H
#define WRITER2_H
#include "writer.h"
#include <boost/serialization/export.hpp>

class writer2 : public writer
{
public:
    inline writer2(){}

    void print_whatever(const std::string s) const override;


private:
    friend class boost::serialization::access;

    template<class Archive>
    void serialize(Archive & ar, unsigned int file_version)
    {
        ar.template register_type<writer2>();
        ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(writer);
    }
};

#endif

writer2.cpp

//writer2.cpp

#include "stdafx.h"
#include "writer2.h"
#include <iostream>

void writer2::print_whatever(const std::string s) const
{
    std::cout << "-=" + s + "=-";
}
BOOST_CLASS_EXPORT_GUID(writer2, "writer2")

genericWriter.h

// genericWriter.h
#ifndef GENERICWRITER_H
#define GENERICWRITER_H

#include "writer.h"

#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/serialization/export.hpp>
#include <boost/serialization/shared_ptr.hpp>

template <typename T>
class genericWriter : public writer
{
public:

    genericWriter(): _methodOfWriting(new T)
    {
        // Yes, the double parentheses are needed, otherwise the comma will be seen as macro argument separator
        BOOST_STATIC_ASSERT((boost::is_base_of<writer, T>::value));
    }

    void print_whatever(const std::string s) const override;

private:
    writer* _methodOfWriting;

    friend class boost::serialization::access;

    template<class Archive>
    void serialize(Archive & ar, unsigned int file_version)
    {
        ar.template register_type<genericWriter<T>>();
        ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(writer);
        ar & BOOST_SERIALIZATION_NVP(_methodOfWriting);
    }
};

genericWriter.cpp

//genericWriter.cpp

#include "stdafx.h"
#include "genericWriter.h"

template<typename T>
void genericWriter<T>::print_whatever(const std::string s) const
{
    (*_methodOfWriting).print_whatever(s);
}

// Exporting a template class not possible?
//BOOST_CLASS_EXPORT_GUID(genericWriter<T>, "genericWriter")

CPlusPlus_Example.cpp

// CPlusPlus_Example.cpp : Defines the entry point for the console application.

#include "stdafx.h"
#include "conio.h"
#include <string>
#include <boost/serialization/assume_abstract.hpp>

#include "genericWriter.h"
#include "writer2.h"
#include <fstream>
#include <boost/archive/xml_iarchive.hpp>
#include <boost/archive/xml_oarchive.hpp>

using namespace std;

int main()
{

    // create and open a character archive for output
    std::ofstream ofs("CPlusPlus_example.xml");

    writer* test = new genericWriter<writer2>();
    test->print_whatever("test");

    // save data to archive
    {
        boost::archive::xml_oarchive oa(ofs);
        // write class instance to archive
        oa << BOOST_SERIALIZATION_NVP(test);
        // archive and stream closed when destructors are called
    }

    {
        // open the archive
        std::ifstream ifs("CPlusPlus_example.xml");
        assert(ifs.good());
        boost::archive::xml_iarchive ia(ifs);

        // restore the schedule from the archive
        ia >> BOOST_SERIALIZATION_NVP(test);
    }

    test->print_whatever("test");

    getch();

    return 0;
}

What I expect it to do

My code should save and load test into/from a XML file (called "CPlusPlus_example.xml" in this example) and should look like the following:

<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<!DOCTYPE boost_serialization>
<boost_serialization signature="serialization::archive" version="15">
<test type="genericWriter" class_id="0" tracking_level="1" version="0" object_id="_0">
    <_methodOfWriting type="writer2" class_id="1" tracking_level="0" version="0">
        <writer></writer>
    </_methodOfWriting>
    <writer></writer>
</test>
</boost_serialization>

My suspects

Because I am somehow not able to boost::export my genericWriter, it may not resolve? But if so - is there any way do such thing using a "generic template class"?

EDIT 1

Based on the comment, I changed the code to the following:

genericWriter.tpp

//genericWriter.tpp

#include "stdafx.h"
#include "genericWriter.h"

template<typename T>
void genericWriter<T>::print_whatever(const std::string s) const
{
    (*_methodOfWriting).print_whatever(s);
}

genericWriter.h

// genericWriter.h
#ifndef GENERICWRITER_H
#define GENERICWRITER_H

#include "writer.h"

#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/serialization/export.hpp>
#include <boost/serialization/shared_ptr.hpp>

#include "genericWriter.tpp"

template <typename T>
class genericWriter : public writer
{
public:

    genericWriter(): _methodOfWriting(new T)
    {
        // Yes, the double parentheses are needed, otherwise the comma will be seen as macro argument separator
        BOOST_STATIC_ASSERT((boost::is_base_of<writer, T>::value));
    }

    //void print_whatever(const std::string s) const override;

private:
    writer* _methodOfWriting;

    friend class boost::serialization::access;

    template<class Archive>
    void serialize(Archive & ar, unsigned int file_version)
    {
        ar.template register_type<genericWriter<T>>();
        ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(writer);
        ar & BOOST_SERIALIZATION_NVP(_methodOfWriting);
    }
};

(And removed the genericWriter.cpp)

Now I get some more errors \(^-^)/

1>e:\vsprograms\dependencyinjectionexamples\cplusplus_example\cplusplus_example\genericwriter.tpp(7): error C2988: unrecognizable template declaration/definition
1>e:\vsprograms\dependencyinjectionexamples\cplusplus_example\cplusplus_example\genericwriter.tpp(7): error C2143: syntax error: missing ';' before '<'
1>e:\vsprograms\dependencyinjectionexamples\cplusplus_example\cplusplus_example\genericwriter.tpp(7): error C2182: 'genericWriter': illegal use of type 'void'
1>e:\vsprograms\dependencyinjectionexamples\cplusplus_example\cplusplus_example\genericwriter.tpp(7): error C2059: syntax error: '<'
1>e:\vsprograms\dependencyinjectionexamples\cplusplus_example\cplusplus_example\genericwriter.tpp(8): error C2143: syntax error: missing ';' before '{'
1>e:\vsprograms\dependencyinjectionexamples\cplusplus_example\cplusplus_example\genericwriter.tpp(8): error C2447: '{': missing function header (old-style formal list?)
1>e:\vsprograms\dependencyinjectionexamples\cplusplus_example\cplusplus_example\writer2.h(12): error C2628: 'writer2' followed by 'void' is illegal (did you forget a ';'?)
1>e:\vsprograms\dependencyinjectionexamples\cplusplus_example\cplusplus_example\writer2.h(12): error C2270: 'print_whatever': modifiers not allowed on nonmember functions
1>e:\vsprograms\dependencyinjectionexamples\cplusplus_example\cplusplus_example\writer2.h(12): error C2259: 'writer2': cannot instantiate abstract class
1>e:\vsprograms\dependencyinjectionexamples\cplusplus_example\cplusplus_example\writer2.h(12): note: due to following members:
1>e:\vsprograms\dependencyinjectionexamples\cplusplus_example\cplusplus_example\writer2.h(12): note: 'void writer::print_whatever(const std::string) const': is abstract
1>e:\vsprograms\dependencyinjectionexamples\cplusplus_example\cplusplus_example\writer.h(17): note: see declaration of 'writer::print_whatever'
1>e:\vsprograms\dependencyinjectionexamples\cplusplus_example\cplusplus_example\writer2.h(15): error C2059: syntax error: 'private'
1>e:\vsprograms\dependencyinjectionexamples\cplusplus_example\cplusplus_example\writer2.h(16): error C2255: 'friend': not allowed outside of a class definition
1>e:\vsprograms\dependencyinjectionexamples\cplusplus_example\cplusplus_example\writer2.h(27): error C2059: syntax error: '}'
1>e:\vsprograms\dependencyinjectionexamples\cplusplus_example\cplusplus_example\writer2.h(27): error C2143: syntax error: missing ';' before '}'
1>e:\vsprograms\dependencyinjectionexamples\cplusplus_example\cplusplus_example\cplusplus_example.cpp(22): error C2061: syntax error: identifier 'genericWriter'

That may be because the definition of the print_whatever function for genericWriter is not inside genericWriter.h anymore?

TheRealVira
  • 1,444
  • 4
  • 16
  • 28
  • Unless I'm missing something, your problem is the most common C++ templates misuse, related to none of your tags. You just can't put a definition of a template in a cpp file and include the declaration in another translation unit. The compiler does nothing with genericWriter.cpp since the template isn't instantiated, and then the linker can't find the function implementation. – Eran May 01 '17 at 08:28
  • 1
    Possible duplicate of [Why can templates only be implemented in the header file?](http://stackoverflow.com/questions/495021/why-can-templates-only-be-implemented-in-the-header-file) – Eran May 01 '17 at 08:29
  • @eran That question is interesting indeed! The problem might be that I do not fully understand the answer (http://stackoverflow.com/a/495056/6901146). Based on my scenario - should I create a genericWriter.tpp, which holds the code that is currently in my genericWriter.cpp? – TheRealVira May 01 '17 at 08:48
  • The key here is to have the definition (i.e., implementation) available to whoever includes the declaration (i.e., the prototype). One way is to place the implementation inside the class declaration (as, e.g., `serialize`). Another option is to put the definition in some other file, and include that other file (e.g., genericWriter.tpp) _from the header_, so whoever includes the header, indirectly includes that other file as well. – Eran May 01 '17 at 08:53

0 Answers0