【问题标题】:Incomplete type in friend function好友功能类型不完整
【发布时间】:2016-09-16 11:52:48
【问题描述】:

这是我的代码的 mcve:(如果重要,Options_proxyOptions 有 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:

  • 我删除了operator!constexpr

  • 在运算符调用之前添加Options_proxy&lt;File_options_impl&gt; 类型的对象:

像这样:

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);

【问题讨论】:

    标签: c++ templates language-lawyer friend incomplete-type


    【解决方案1】:

    看起来这是一个 gcc 错误。这是一个 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 的相同程序,或者如果您在auto&amp;&amp; a = f((X*)0); 之前显式实例化A&lt;0&gt;(例如使用template struct A&lt;0&gt;;)。这表明 gcc 的问题在于类模板隐式实例化 [temp.inst]:

    1 - 除非类模板特化已显式实例化 (14.7.2) 或显式特化 (14.7.3), 当在上下文中引用特化时,类模板特化被隐式实例化 需要完全定义的对象类型或当类类型的完整性影响语义时 程序。

    constexpr A&lt;0&gt; f(X*)return 语句需要类模板 A&lt;0&gt;,因此此时应隐式实例化。尽管友元函数定义在词法上在类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&lt;0&gt;; 显式实例化来解决):

    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>;
    

    【讨论】:

      【解决方案2】:

      我在您的代码中看到的一个问题是多个实现可能具有相同的 ::Flag 成员,这意味着您的朋友运算符可能会被多次定义,这违反了一个定义规则。

      而且 Options_proxy 没有任何私有成员,所以你不需要让操作员成为朋友(我认为你滥用朋友来定义一个外部函数内联)。

      您需要为您的运营商一个明确的定义,我认为当前签名不可能。


      如果保证标志是唯一的,您可以尝试将运算符移到 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);
      };
      

      【讨论】:

      • 感谢您的回答。 1.不同的专业会有不同的Flag成员。如果Impl::Flag 是别名,它们最终可能拥有相同的Flag。在当前系统中不会发生这种情况。 2. 正如我所说,这是一个 mcve,所以真正的代码要复杂得多。我同意这有点滥用朋友声明,但我看不出有任何其他方法可以在 File_options_impl 之外定义 Flag 的运算符(在代码的库部分中)。 3. 我不太确定这不可能。如果不可能,我会非常失望。
      猜你喜欢
      • 2019-03-25
      • 2012-05-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-10-25
      • 2020-07-19
      • 2011-03-27
      • 1970-01-01
      相关资源
      最近更新 更多