9

I have the following code (more than one file involved)...

//--- SomeInterface.h
struct SomeInterface
{
  virtual void foo() = 0;
  virtual ~SomeInterface(){}
};

//--- SomeInterfaceUser.h
#include <memory> //shared_ptr

class SomeInterface;
//NOTE: struct SomeInterface... causes linker error to go away...

class SomeInterfaceUser
{
  public:
    explicit SomeInterfaceUser(std::shared_ptr<SomeInterface> s);
};

//SomeInterfaceUser.cpp
#include "SomeInterfaceUser.h"
#include "SomeInterface.h"
SomeInterfaceUser::SomeInterfaceUser(std::shared_ptr<SomeInterface> s)
{
}

//SomerInterfaceUserInstantiator.cpp
#include "SomeInterfaceUser.h"
#include "SomeInterfaceImpl.h"

struct SomeInterfaceImpl : SomeInterface
{
  virtual void foo(){}
};

void test()
{
  SomeInterfaceUser x{std::make_shared<SomeInterfaceImpl>()};
}

Using the Visual C++ compiler, I get a linker error (LNK2019). Using GCC 4.8.4 this is not the case. Changing the forward declaration class SomeInterface to struct SomeInterface makes the linker error go away. I always thought that one should be able to use class/struct interchangeably? The interface of SomeInterfaceUser should not depend on whether SomeInterface is defined as class or struct, not so?

Is this a Visual C++ bug. I cannot find anything relating to it. I suspect the fact that the struct is used as template parameter has something to do with it.

Your help appreciated.

cpplearner
  • 13,776
  • 2
  • 47
  • 72
Werner Erasmus
  • 3,988
  • 17
  • 31
  • Seems to work fine here. Please post all your compiler and linker settings and make sure that you've correctly posted all file contents. – Christian Hackl Nov 02 '15 at 17:51
  • @ChristianHackl. I have quite a complex project, but changing a forward declaration from struct to class should not make a linker error go away. Have you moved the various classes to their respective files? – Werner Erasmus Nov 02 '15 at 18:09
  • Well, does the error only happen in your complex project or does it also happen with the example code you have posted? – Christian Hackl Nov 02 '15 at 18:14
  • @Christian Hackl. The example is a simplification. I'll have to see, to be honest. – Werner Erasmus Nov 02 '15 at 18:18
  • OK. I don't know, personally. I know that VC has a compiler warning for `struct`/`class` mismatches, but I am not personally aware of linker errors. Then again, I've never had unmodifiable code with this problem. – Christian Hackl Nov 02 '15 at 18:20
  • I might be way off with C++ standard, but `struct` and `class` are the same except that for `struct` the access specifier defaults to `public` while for `class` to `private`. For example in VC2k12 a minimal example that I created works (but it throws a warning _C4624_ - related to destructor). Try using `class` and make the members `public` (or `protected` where appropriate). – CristiFati Nov 02 '15 at 18:25
  • @nm, Just following the part that you cite. I've always class/struct interchangeable (at least 10 years now). Here is another answer referring to the same thing: http://stackoverflow.com/questions/4866425/mixing-class-and-struct – Werner Erasmus Nov 02 '15 at 18:37
  • @nm, I've found the following excerpt from Herb Sutters xc++ errata (http://www.gotw.ca/publications/xc++-errata.htm): "It's perfectly legal and standards-conforming to forward-declare a class as a struct and vice versa. In most of the book I've tended to avoid doing that, though. Why?.... Sigh." Still not sure about the verdict. – Werner Erasmus Nov 02 '15 at 18:42
  • 4
    I've seen this before and always fixed the problem by forward declaring and declaring them using the same keyword. I think it has something to do with the way MSVC mangles names. – legalize Nov 02 '15 at 20:47

1 Answers1

9

I've just been facing the same problem with both VC++ 2010 and VC++ 2017, and after some tests I've found that the problem resides in the symbol name the compiler gives to structs and classes internally.

Here a minimum example consisting in three files:

main.cpp

#include "bar.h"
struct Foo {};

int main() {
  Foo foo;
  bar(&foo);
  return 0;
}

bar.h

class Foo;
void bar(Foo* f);

bar.cpp

#include "bar.h"

void bar(Foo* foo) {}

When the project is compiled the following errors and warnings appear:

warning C4099: 'Foo': type name first seen using 'class' now seen using 'struct'

see declaration of 'Foo'

error LNK2019: unresolved external symbol "void __cdecl bar(struct Foo *)" (?bar@@YAXPAUFoo@@@Z) referenced in function _main

fatal error LNK1120: 1 unresolved externals

Now, I swapped the struct and class declarations, so main.cpp and bar.h are now:

main.cpp

#include "bar.h"
class Foo {};

int main() {
  Foo foo;
  bar(&foo);
  return 0;
}

bar.h

struct Foo;
void bar(Foo* f);

As expected, the error still pops up:

error LNK2019: unresolved external symbol "void __cdecl bar(class Foo *)" (?bar@@YAXPAVFoo@@@Z) referenced in function _main

BUT, and this is the interesting part, see that the symbols for the expected function (the one used in main()) in each case differ:

  • ?bar@@YAXPAUFoo@@@Z (when the parameter is a struct)

  • ?bar@@YAXPAVFoo@@@Z (when the parameter is a class)


Conclusion

The compiler gives slightly different names if the type is a struct or a class.

The linker then cannot find the proper definition because it is looking for a different one: bar.cpp defines one with the forward declaration, but for the moment it is called in main.cpp the actual declaration has taken placed, so a different function name is given in the symbols table.

Update (2023-02-03)

I've just seen that clang is able to report this issue:

error: 'Foo' defined as a struct here but previously declared as a class; this is valid, but may result in linker errors under the Microsoft C++ ABI [-Werror,-Wmismatched-tags]

cbuchart
  • 10,847
  • 9
  • 53
  • 93
  • 2
    Just bumped on the same issue, and took me a 2~3 hours to figure out the reason. Never thought that MSVC(VS 2017) makes the distinction up to a point of issuing a linker error because of this. – aep Mar 01 '19 at 04:16