I have a situation in which I need to have objects that can react differently to incoming information (think handling network messages for example). Following the example here, I've built a working solution (code below). Well, at least it's working when compiled with MSVC. But then, I tried to compile it with other compilers, and I've found that they don't behave all the same: some warn me about an "invalid use of incomplete type class Foo" (but the code runs fine) when others consider it an error and don't even compile. I understand what this warning/error means. My question is: why is this working with some compilers and not others? Is this valid C++ code that some compilers don't handle? And if it is not, is there a way to make it C++ compliant?
Here is a detailed list of all compilers/versions tested with the minimal code below:
Compiler | Outcome |
---|---|
clang 6.0 | Error |
clang 11.0 | Error |
gcc 6.5 | Error |
gcc 7.1 | Warning, works fine |
gcc 10.2 | Warning, works fine |
MSVC 19.00 | No warning, works fine |
MSVC 19.28 | No warning, works fine |
Here is my minimum reproducible example I used to test with multiple compilers:
#include <iostream>
// The base class all the "network messages" will inherit from
class Base
{
};
// Forward declaration of the handler Foo class (and the troublemaking line)
class Foo;
// Curiously recurring pattern class to have a Dispatch function calling the
// right Bar function in Foo objects
template<typename TDerived>
class BaseCRP : public Base
{
public:
void Dispatch(Foo* foo)
{
foo->Bar(static_cast<TDerived&>(*this));
}
};
// All our N derived classes from Base
class Derived1 : public BaseCRP<Derived1>
{
};
class Derived2 : public BaseCRP<Derived2>
{
};
// This creates a class with a Bar function for each
// type in the TAllTuple tuple passed
template<typename TBase, typename TAllTuple>
class GenericFoo;
template <typename TBase, typename TFirst, typename... TRest>
class GenericFoo<TBase, std::tuple<TFirst, TRest...> > : public GenericFoo<TBase, std::tuple<TRest...> >
{
public:
using GenericFoo<TBase, std::tuple<TRest...> >::Bar;
virtual void Bar(TFirst& derived)
{
this->Bar(static_cast<TBase&>(derived));
}
};
template<typename TBase>
class GenericFoo<TBase, std::tuple<> >
{
public:
virtual void Bar(TBase&)
{
}
};
// Actually create the Foo class, with a Bar function for
// each of our DerivedN class
class Foo : public GenericFoo<Base, std::tuple<Derived1, Derived2>>
{
};
// Now we can inherit Foo and specify how we want
// to react to each Derived. RealFoo1 only reacts to Derived1
class RealFoo1 : public Foo
{
public:
virtual void Bar(Derived1& d) override
{
std::cout << "Derived1" << std::endl;
}
};
// RealFoo2 reacts to both Derived1 and Derived2
class RealFoo2 : public Foo
{
public:
virtual void Bar(Derived1& d) override
{
std::cout << "Derived1" << std::endl;
}
virtual void Bar(Derived2& d) override
{
std::cout << "Derived2" << std::endl;
}
};
int main(int argc, char* argv[])
{
RealFoo1 rf1;
RealFoo2 rf2;
Derived1 d1;
Derived2 d2;
std::cout << "Process d1 with rf1:" << std::endl;
d1.Dispatch(&rf1);
std::cout << "Process d1 with rf2:" << std::endl;
d1.Dispatch(&rf2);
std::cout << "Process d2 with rf1:" << std::endl;
d2.Dispatch(&rf1);
std::cout << "Process d2 with rf2:" << std::endl;
d2.Dispatch(&rf2);
return 0;
}
Expected output (and actual output when it works):
Process d1 with rf1:
Derived1
Process d1 with rf2:
Derived1
Process d2 with rf1:
Process d2 with rf2:
Derived2
Warning:
In member function 'void BaseCRP<TDerived>::Dispatch(Foo*)':
<source>:20:12: warning: invalid use of incomplete type 'class Foo'
foo->Bar(static_cast<TDerived&>(*this));
^~
<source>:10:7: note: forward declaration of 'class Foo'
class Foo;
^~~
Error:
<source>:20:12: error: member access into incomplete type 'Foo'
foo->Bar(static_cast<TDerived&>(*this));
^
<source>:10:7: note: forward declaration of 'Foo'
class Foo;