友情函数中的不完整类型

时间:2016-09-16 11:52:48

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

这是我的代码的一部分:(如果重要的话,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 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);

2 个答案:

答案 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);
};