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; }
error C2039: 'm': is not a member of 'Foo'
– DaybreakFoo::
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