Incomplete type in friend function

This is the mcve of my code: (if that matters, Options_proxy and Options have constexpr ctors). I know that he is still far from simple, but could not simplify it while still showing an error:

 template <class Impl> struct Options_proxy : Impl { using Flag = typename Impl::Flag; friend constexpr auto operator!(Flag f) -> Options_proxy { return {}; // <-- error here }; }; template <class Impl> struct Options : Impl { using Flag = typename Impl::Flag; }; struct File_options_impl { enum class Flag : unsigned { nullflag, read, write }; friend constexpr auto operator!(Flag f) -> Options_proxy<File_options_impl>; }; using File_options = Options<File_options_impl>; auto foo() { !File_options::Flag::write; // <-- required from here } 

gcc 6 and 7 give this error:

 In instantiation of 'constexpr Options_proxy<File_options_impl> operator!(Options_proxy<File_options_impl>::Flag)': required from ... etc etc... error: return type 'struct Options_proxy<File_options_impl>' is incomplete 

clang compiles it in order.

The code matches gcc if:

  • I delete the constexpr operator!

or

  • before calling the operator, add an object of type Options_proxy<File_options_impl> :

like this:

 auto foo() { Options_proxy<File_options_impl> o; !File_options::Flag::write; // <-- now OK in gcc also } 

Is this a gcc bug or is it some Undefined Behavior in the code, something like vague or no diagnostics needed?


Regarding the motivation for writing such code:

I want to create (for fun mostly) a safe flag / parameter system (without macros):

Library of black magic:

 template <class Impl> requires Options_impl<Impl> struct Options : Impl { // go crazy }; 

User Code:

 struct File_options_impl { // create a system where here the code // needs to be as minimal as possible to avoid repetition and user errors // this is what the user actually cares about enum class Flag : unsigned { nullflag = 0, read = 1, write = 2, create = 4}; // would like not to need to write this, // but can't find a way around it friend constexpr auto operator!(Flag f1) -> Options_proxy<File_options_impl>; friend constexpr auto operator+(Flag f1, Flag f2) -> Options_proxy<File_options_impl>; friend constexpr auto operator+(Flag f1, Options_proxy<File_options_impl> f2) -> Options_proxy<File_options_impl>; }; using File_options = Options<File_options_impl>; 

and then:

 auto open(File_options opts); using F_opt = File_options::Flag; open(F_opt::write + !F_opt::create); 
+6
source share
2 answers

This seems to be a gcc bug. Here's the MCVE (4 lines):

 struct X; template<int> struct A { friend constexpr A f(X*) { return {}; } }; // In instantiation of 'constexpr A<0> f(X*)': // error: return type 'struct A<0>' is incomplete struct X { friend constexpr A<0> f(X*); }; auto&& a = f((X*)0); 

This is accepted by clang and MSVC.

As you noticed, gcc accepts the same program without constexpr , or if you explicitly create an instance of A<0> (for example, from template struct A<0>; ) to auto&& a = f((X*)0); . This suggests that the gcc issue has an implicit template instance [temp.inst] :

1 - If the specification of the class template has not been explicitly created (14.7.2) or explicitly specialized (14.7.3), the specialization of the class template is implicitly created when the specialization is referenced in a context that requires a fully defined type of object or when the completeness of the class type affects semantics programs.

A class template A<0> is required in the expression return constexpr A<0> f(X*) , so it must be implicitly created at this point. Although the definition of a friend's function is lexical in class A , the class should not be considered incomplete in the definition of a friend's function; For example, the following universal program:

 struct Y; struct B { friend constexpr B f(Y*) { return {}; } }; struct Y { friend constexpr B f(Y*); }; auto&& b = f((Y*)0); 

Interestingly, both gcc and clang (although not MSVC) have problems with the following program (again, fixed by removing constexpr or an explicitly created instance using template struct C<0>; ):

 struct Z; template<int> struct C { friend constexpr C* f(Z*) { return 0; } }; struct Z { friend constexpr C<0>* f(Z*); }; // error: inline function 'constexpr C<0>* f(Z*)' used but never defined auto&& c = f((Z*)0); 

I would suggest using an explicit instance workaround. In your case, it will be:

 template class Options_proxy<File_options_impl>; 
+1
source

One problem that I see with your code is that multiple implementations may have the same :: Flag element, which means your friendโ€™s statement is potentially defined several times, violating one definition rule.

Also, Options_proxy does not have any private members, so you do not need to make a friend operator (I think you are abusing a friend to define an external inline function).

You need an unambiguous definition for your operator, and I do not think that this is possible with the current signature.


Given that the flags are unique, you can try to move the statement outside of Options_proxy.

 template <class Impl> struct Options_proxy : Impl { using Flag = typename Impl::Flag; }; template <class Impl, typename Flag = Impl::Flag> constexpr Options_proxy<Impl> operator!(Flag f) { return {}; } template <class Impl> struct Options : Impl { using Flag = typename Impl::Flag; }; struct File_options_impl { enum class Flag : unsigned { nullflag, read, write }; friend constexpr Options_proxy<File_options_impl> operator!(Flag f); // Or if that doesn't work: friend constexpr Options_proxy<File_options_impl> operator!<File_options_impl>(Flag f); }; 
0
source

All Articles