朋友功能中的不完整类型

bol*_*lov 6 c++ templates friend language-lawyer incomplete-type

这是我的代码的一部分:(如果重要,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
}
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

clang编译好了.

如果符合以下条件,代码符合gcc:

  • 我删除constexproperator!

要么

  • Options_proxy<File_options_impl>在操作员调用之前添加一个类型的对象:

像这样:

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

这是gcc错误还是代码中的一些未定义行为,如未指定或无需诊断?


至于写这样的代码的动机:

我想创建(主要是为了好玩)一个类型安全标志/选项系统(没有宏):

图书馆黑魔法:

template <class Impl>
  requires Options_impl<Impl>
struct Options : Impl {
   // go crazy
};
Run Code Online (Sandbox Code Playgroud)

用户代码:

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>;
Run Code Online (Sandbox Code Playgroud)

然后:

auto open(File_options opts);

using F_opt = File_options::Flag;
open(F_opt::write + !F_opt::create);
Run Code Online (Sandbox Code Playgroud)

eca*_*mur 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);
Run Code Online (Sandbox Code Playgroud)

这被 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);
Run Code Online (Sandbox Code Playgroud)

有趣的是,gccclang(尽管不是 MSVC)在执行以下程序时都遇到问题(再次通过删除constexpr或显式实例化来修复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);
Run Code Online (Sandbox Code Playgroud)

我建议使用显式实例化解决方法。在你的情况下,这将是:

template class Options_proxy<File_options_impl>;
Run Code Online (Sandbox Code Playgroud)