这是我的代码的一部分:(如果重要的话,Options_proxy
和Options
都有constexpr ctors)。我知道它仍然远非简单,但仍然无法简化它,同时仍然表现出错误:
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和7给出了这个错误:
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编译好了。
代码符合gcc if:
constexpr
operator!
或
Options_proxy<File_options_impl>
类型的对象:auto foo()
{
Options_proxy<File_options_impl> o;
!File_options::Flag::write; // <-- now OK in gcc also
}
这是gcc错误还是代码中有一些未定义的行为,如未指定或无需诊断?
关于编写此类代码的动机:
我想创建(主要是为了好玩)一个类型安全标志/选项系统(没有宏):
图书馆黑魔法:
template <class Impl>
requires Options_impl<Impl>
struct Options : Impl {
// go crazy
};
用户代码:
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>;
然后:
auto open(File_options opts);
using F_opt = File_options::Flag;
open(F_opt::write + !F_opt::create);
答案 0 :(得分:1)
看起来这是一个gcc bug。这是一个MCVE(4行):
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);
这是clang和MSVC接受的。
正如您所观察到的,gcc在没有constexpr
的情况下接受相同的程序,或者如果您在A<0>
之前明确地实例化template struct A<0>;
(例如使用auto&& a = f((X*)0);
)。这表明gcc存在的问题是在类模板中隐式实例化 [temp.inst] :
1 - 除非已明确实例化(14.7.2)或明确专门化(14.7.3)类模板特化, 在上下文中引用特化时,将隐式实例化类模板特化 这需要完全定义的对象类型,或者类类型的完整性会影响语义 该计划。
A<0>
return
语句需要类模板constexpr A<0> f(X*)
,因此应该在此时隐式实例化。虽然友元函数定义在词法A
中是词法上的,但在友元函数的定义中不应该认为该类是不完整的;例如以下非模板程序被普遍接受:
struct Y;
struct B { friend constexpr B f(Y*) { return {}; } };
struct Y { friend constexpr B f(Y*); };
auto&& b = f((Y*)0);
有趣的是,gcc 和 clang(虽然不是MSVC)在使用以下程序时遇到问题(同样,通过删除constexpr
或使用template struct C<0>;
显式实例化来修复):< / p>
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);
我建议使用显式实例化变通方法。在你的情况下:
template class Options_proxy<File_options_impl>;
答案 1 :(得分:0)
我在您的代码中看到的一个问题是,多个实现可能具有相同的:: Flag成员,这意味着您的朋友操作员可能会多次定义,违反了一个定义规则。
此外,Options_proxy没有任何私人会员,因此您不需要让运营商成为朋友(我认为您正在滥用朋友来内联定义外部功能)。
您需要为您的运营商制定一个明确的定义,而且我认为现有签名无法做到这一点。
鉴于Flags是唯一的保证,您可以尝试将操作符移到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);
};