Member function of incomplete class as friend = formally valid?
Asked Answered
B

2

3

The following code, where the nested class Info designates two member functions of the outer class Impl as friends, compiles nicely with Visual C++ and g++, with the code as given below.

But, if the member functions aren't declared before Info, then g++ complains about incomplete class rather than missing declaration:

In file included from ../console_io.hpp:6:0,
                 from console_io.cpp:1:
../console_io.impl.windows.hpp:69:47: error: invalid use of incomplete type 'class console::Display::Impl'
         friend auto Impl::api_info() const -> Ref_<const Api_info>;
                                               ^
../console_io.impl.windows.hpp:62:20: note: definition of 'class console::Display::Impl' is not complete until the closing brace
     class Display::Impl
                    ^
../console_io.impl.windows.hpp:70:47: error: invalid use of incomplete type 'class console::Display::Impl'

Minimal example:

struct Foo
{
    class Bar
    {
        friend void Foo::m();
    };

    void m(){}
};
foo.cpp:5:28: error: invalid use of incomplete type 'struct Foo'
         friend void Foo::m();
                            ^
foo.cpp:1:8: note: definition of 'struct Foo' is not complete until the closing brace
 struct Foo
        ^

And so by association I wonder about the formal validity of making a member function of T a friend, at a point where T is not yet complete.


I present the actual original code that compiles, so that, if it's formally invalid, reasonable alternatives for this use case can be suggested. I think that I, as the one who's asking, am possibly the least competent to decide what's relevant or not for answers. Notation: Ref_<T> means T&.

class Display::Impl
{
private:
    using Api_info = impl::winapi::Console_screen_buffer_info;

    inline auto api_info() const -> Ref_<const Api_info>;   // Def. after class.
    inline void invalidate_api_info();                      // Def. after class.

    class Info
    {
    friend auto Impl::api_info() const -> Ref_<const Api_info>;
    friend void Impl::invalidate_api_info();
    private:
        bool        is_valid_   = false;
        Api_info    api_info_   = {};
    };

    // State:
    impl::winapi::Handle    text_buffer_;
    mutable Info            info_;

    Impl( Ref_<const Impl> ) = delete;
    auto operator=( Ref_<const Impl> ) -> Ref_<Impl> = delete;

public:
    auto size() const
        -> Point
    {
        auto const api_size = api_info().dwSize;
        return Point{ api_size.x, api_size.y };
    }

    ~Impl()
    {}      // TODO:

    Impl( const Point size )
        : text_buffer_( impl::winapi::CreateConsoleScreenBuffer(
            impl::winapi::flag_GENERIC_READ | impl::winapi::flag_GENERIC_WRITE,
            0,              // No sharing
            nullptr,        // Default security.
            impl::winapi::flag_CONSOLE_TEXTMODE_BUFFER, // The allowed value.
            nullptr         // Reserved.
            ) )
    {
        hopefully( text_buffer_ != impl::winapi::invalid_handle_value )
            || fail( "console::Display: CreateConsoleScreenBuffer failed" );
    }
};

auto Display::Impl::api_info() const
    -> Ref_<const Api_info>
{
    if( not info_.is_valid_ )
    {
        impl::winapi::GetConsoleScreenBufferInfo( text_buffer_, &info_.api_info_ )
            || fail( "GetConsoleScreenBufferInfo failed" );
        info_.is_valid_ = true;
    }
    return info_.api_info_;
}

void Display::Impl::invalidate_api_info()
{ info_.is_valid_ = false; }
Bogor answered 24/4, 2016 at 8:50 Comment(6)
Hmm, I've just tried to build a minimal complete verifiable example ;-), and, well, it seems it's compilable. What version of gcc do you use?Hannan
@user3159253: MingW g++ 5.1.0Bogor
The minimal example doesn't compile with Visual C++, either (at least with the 2015 version): error C2039: 'm': is not a member of 'Foo'Daybreak
I think the problem is the qualified name lookup from Foo:: as specified by [class.qual] (3.4.3.1): [...] the name specified after the nested-namespecifier is looked up in the scope of the class (10.2), except for the cases listed below. The name shall represent one or more members of that class or of one of its base classes (Clause 10). [...]. I read it as you cannot refer to a qualified-id if it has not been declared yet.Peay
@ChristianHackl: Yeah, but that's expected. The problem is g++'s mention of incomplete class. Which raises the question of whether the corresponding code that compiles, with func moved to before the inner class, is formally valid.Bogor
I think the core of the problem is nesting classes. I'll show a simpler example that compilesHannan
P
1

After a bit of digging, I think this is what you are looking for:

§9.3 [class.mfct]:

7 Previously declared member functions may be mentioned in friend declarations.

So (as far as I understand the standard), your code is valid when you declare the member function before your nested class.

Peay answered 24/4, 2016 at 10:15 Comment(1)
I think this is it, because it doesn't seem meaningful except for the case at hand: an inner class with a friend declaration.Bogor
H
0

Here's a stripped down example which perfectly compiles even with gcc-4.7 from Debian 7:

class B;

class C {
    inline auto f() const -> B&;
    class In {
        friend auto C::f() const -> B&;
        int i;
    };
};

class B {
    int b;
};

auto C::f() const -> B& {
    static B b;
    return b;
};

So I guess the problem is in nesting classes.

Hannan answered 24/4, 2016 at 10:40 Comment(3)
It does not compile if you remove the declaration of C::f before In or if you try to make a friend of B::f before the definition of B which is the OP's question.Peay
Well, the definition of B is after the friend clause. It's required indeed, because otherwise I couldn't define C::f(). Declaration of B in the beginning is also required, and it should go before C definition, obviously (otherwise one gets B doesn't name a type error in f() definition). So I still can't understand why this example couldn't be a model.Hannan
The definition of B is after the friend but the friend does not refer to a qualified-id within B, it refers to a qualified-id within C that returns a B&, it is not the same. The OP's question is weither or not you have the right to have a friend in a nested class referring to a method of the owner class if the method is declared in the owner class before the nested class.Peay

© 2022 - 2024 — McMap. All rights reserved.