0

******* EDIT *******

One reader suggested that my question was a duplicate of the question found here. The leading response to this question gives two solutions:

  • 1) #include the implementation file at the end of the header (which is what I did in my solution) or
  • 2) explicitly instantiate all of the template instances I'll need at the end of the implementation file

Since I had already attempted this solution, this is not a duplicate question.

The problem, as addressed by Ben Voigt below (thank you), has to do with Visual Studio. Changing the "Item Type" from "C/C++ compiler" to "C/C++ header" for the MyClass.cpp file resolved the issue.

**** END EDIT ****

I'm a beginner to c++ and I'm running into an issue trying to compile the following program in Visual Studio 2015. The program compiled fine using the clang compiler on my Mac. The program also compiled fine using cl.exe from the Developer Command Prompt for VS2015. I guess this means something is configured wrong in my Visual Studio project, but I haven't found a solution.

Here's the code:

/** @file MyInterface.h */

#ifndef MY_INTERFACE_
#define MY_INTERFACE_

template<class ItemType>
class MyInterface
{
public:
    virtual void sayHello() const = 0;
};

#endif

/** @file MyClass.h */

#ifndef MY_CLASS_
#define MY_CLASS_

#include "MyInterface.h"

template<class ItemType>
class MyClass : public MyInterface<ItemType>
{
private:
    ItemType myItem;

public:
    MyClass();
    void setItem(const ItemType& newItem);
    void sayHello() const;
};

#include "MyClass.cpp"
#endif

/** Implementation file for MyClass.
    @file MyClass.cpp */

#include "MyClass.h"
#include <iostream>

template<class ItemType>
MyClass<ItemType>::MyClass()
{
}

template<class ItemType>
void MyClass<ItemType>::setItem(const ItemType& newItem)
{
    myItem = newItem;
}

template<class ItemType>
void MyClass<ItemType>::sayHello() const
{
    std::cout << "Hello! My Item is: " << myItem << std::endl;
}

/** @file test.cpp */

#include "MyClass.h"
#include <string>

int main()
{
    MyClass<std::string> classOne;
    classOne.setItem("foo");
    classOne.sayHello();

    MyClass<int> classTwo;
    classTwo.setItem(7);
    classTwo.sayHello();
}

When I combine these files in one directory and run the command:

cl test.cpp

from the Developer Command Prompt for VS2015, it correctly compiles. I get a test.exe executable file which returns:

test.exe
Hello! My item is: foo
Hello! My item is: 7

So far, so good.

The problem begins when I try to complete this project inside of Visual Studio 2015. I started an "Empty Project" for Visual c++ and added these exact same files. However, when I build the project I get the error messages:

Error   C2995   'MyClass<ItemType>::MyClass(void)': function template has already been defined
Error   C2995   'void MyClass<ItemType>::setItem(const ItemType &)': function template has already been defined
Error   C2995   'void MyClass<ItemType>::sayHello(void) const': function template has already been defined

The error message leads me to believe that I have a circular dependency. The culprit appears to be the #include "MyClass.cpp" in the MyClass.h file, but this is necessary for the template to compile. Without this inclusion, the compiler cannot see the instantiation of the template and does not know the actual data type corresponding to the generic type ItemType (or so my textbook says).

In an effort to satisfy the Visual Studio build process, I removed the #include "MyClass.cpp from the MyClass.h file, but then I get THESE errors:

Error   LNK2019 unresolved external symbol "public: __thiscall MyClass<class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > >::MyClass<class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > >(void)" (??0?$MyClass@V?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@@@QAE@XZ) referenced in function _main
Error   LNK2019 unresolved external symbol "public: __thiscall MyClass<int>::MyClass<int>(void)" (??0?$MyClass@H@@QAE@XZ) referenced in function _main
Error   LNK2019 unresolved external symbol "public: void __thiscall MyClass<int>::setItem(int const &)" (?setItem@?$MyClass@H@@QAEXABH@Z) referenced in function _main

There were six such errors (only three are shown), all being unresolved external symbols.

At this point I don't know what else to try. If I include the implementation file in the header, the compiler yells at me for having circular dependencies, and if I DON'T include the implementation file in the header, I have unresolved external symbols.

Any help on this problem would be much appreciated :)

Community
  • 1
  • 1
Beelzebub
  • 23
  • 4
  • 3
    Template definitions need to be in the .h file, and you should never #include a .cpp file.. –  Jan 05 '17 at 22:37
  • http://stackoverflow.com/questions/495021/why-can-templates-only-be-implemented-in-the-header-file – drescherjm Jan 05 '17 at 22:43
  • 2
    @latedeveloper: That's a convention, not a NEED. – Ben Voigt Jan 05 '17 at 22:43
  • @Ben It's a need when you are learning. And in fact I've never come across a case where I've NEEDED to do otherwise. –  Jan 05 '17 at 22:47
  • @latedeveloper: The opposite of "always X" is not "never X", it's "sometimes not X". – Ben Voigt Jan 05 '17 at 22:50
  • Well, but the fact remains that the extension .cpp should be reserved for files that are supposed to be translation units. Rename MyClass.cpp to MyClass_impl.h, and it should compile fine. – JohnB Jan 05 '17 at 22:53
  • 1
    @JohnB: Not a fact, but a convention. A convention followed by the IDE, for example. – IInspectable Jan 05 '17 at 22:54
  • 1
    "should" = convention. The fact remains that the convention exists. That's what we both mean, I suppose. – JohnB Jan 05 '17 at 22:55

1 Answers1

2

Because you #include MyClass.cpp, it isn't a compilation unit itself, it's a header file. Make sure Visual Studio is told not to try to build it separately, by default it assumes .h files are headers and .cpp are each individually compiled units.

Access the item properties and change "Item Type" from "C/C++ compiler" to "C/C++ header".

enter image description here

This effectively changes the build command to the working version

cl test.cpp

from the default (effective) command generated by adding all the files

cl test.cpp MyClass.cpp

which would cause exactly the same errors if you typed that at the command line.

There are other ways to accomplish removing it from the build command, like "Exclude file from project", found on the right-click menu in Solution Explorer, but that makes it less convenient to edit/navigate to. Marking it correctly as a header file will give you the best experience for Intellisense auto-completion, Goto Definition, and source view during debugging.

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720