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; }
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With