This is a mcve of my code: (if it matters, Options_proxy
and Options
have constexpr ctors). I am aware it's still far from simple, but couldn't simplify it more while still exhibiting the 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 OK.
The code complies in gcc if:
- I remove the
constexpr
of theoperator!
or
- add an object of type
Options_proxy<File_options_impl>
before the operator call:
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 some Undefined Behavior in the code, something like unspecified or no diagnostics required?
As for motivation of writing such code:
I want to create (for fun mostly) a type safe flag/options system (without macros):
Library 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);