3

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;
cigien
  • 57,834
  • 11
  • 73
  • 112
adepierre
  • 73
  • 1
  • 5
  • 1
    The code is invalid, `Foo` is used before it's defined. Move `Dispatch()` definition out-of-line to after `Foo` definition. – rustyx Dec 25 '20 at 19:46
  • I **know** that. The question is about the differences between the compilers. Why does this work with MSVC and modern GCC in particular. And in cases where you don't define everything in one file like here but have ``BaseCRP`` in its own .hpp file, you can't actually move the ``Dispatch()`` function after ``Foo`` definition. Hence the forward declaration. – adepierre Dec 25 '20 at 19:59
  • "*Is this valid C++ code that some compilers don't handle?*", hence my initial comment. MSVC is famous for not implementing two phase lookup correctly, don't know about GCC, could be a compiler extension. – rustyx Dec 25 '20 at 20:20
  • You could minimize your code to `class Foo; template void f(Foo* foo) { foo->Bar(); }`. – Language Lawyer Dec 26 '20 at 17:24
  • Does this answer your question? [Template method accesses forward declared class fails to compile only without this pointer](https://stackoverflow.com/questions/60215335/template-method-accesses-forward-declared-class-fails-to-compile-only-without-th) – Language Lawyer Dec 26 '20 at 17:31

0 Answers0