通过CRTP模式传递的单元化副本按值传递

时间:2015-08-17 09:51:13

标签: c++

我正在使用CRTP模式,并尝试定义适用于其实现的运算符。我发现了未初始化对象的奇怪行为。

CRTP基类:

template < class C >
struct CRTP
{
  using self_t = C;
  const self_t& self() const
  { return static_cast<const self_t&>(*this); }
  self_t& self()
  {
    const CRTP& cs = static_cast<const CRTP&>(*this);
    return const_cast<self_t&>(cs.self());
  }
  void printValue()
  { cout << "CRTP value : " << self().getValue() << endl; }
};

实施1:

struct Impl : public CRTP<Impl> {
  Impl()            = default;
  Impl(Impl&&)      = default;
  Impl(const Impl&) = default;
  explicit
  Impl(int i) : v(i) { }

  friend void swap(Impl& l, Impl& r)
  { using std::swap; swap(l.v, r.v); }

  Impl& operator=(Impl o)
  { swap(*this, o); return *this; }

  int getValue() const
  { return v; }

private:
  int v;
};

实施2:

template < class Arg >
struct Neg : public CRTP< Neg<Arg> >
{
  Neg()           = default;
  Neg(Neg&&)      = default;
  Neg(const Neg&) = default;
  explicit
  Neg(Arg arg) : a(arg) { }

  friend void swap(Neg& l, Neg& r)
  { using std::swap; swap(l.a, r.a); }

  Neg& operator=(Neg o)
  { swap(*this, o); return *this; }

  int getValue() const
  { return -a.getValue(); }
 private:
  Arg a;
};

运算符(operator!正常,operator~显示问题):

template < class C >
Neg<C> operator~(CRTP<C> v)
{ return Neg<C>(std::move(v.self())); }

template < class C >
Neg<C> operator-(const CRTP<C>& v)
{ return Neg<C>(v.self()); }

template < class C >
Neg<C> operator-(CRTP<C>&& v)
{ return Neg<C>(std::move(v.self())); }

现在有了一个简单的主要内容:

int main(void)
{
  auto n = -Impl(10);
  n.printValue();
  n = ~Impl(20);
  n.printValue();
}

使用gcc编译,我有:

CRTP value : -10
CRTP value : 0

用CLANG编译我得到:

CRTP value : -10
CRTP value : -1186799704

现在我有两个问题:

  • 是这个行为标准吗?即使我删除默认值,也会发生 构造
  • 如何通过值传递&#34;中的运算符 风格&#34; ?我有想要使用的n-ary运算符 可变参数模板,我不能枚举左值和左值参考的每个组合。

1 个答案:

答案 0 :(得分:5)

您正在CRTP<C>参数中对对象进行切片。您按价值计算CRTP,这意味着v子对象之外的任何内容(例如成员aCRTP<C>)都会被切掉。然后,您通过将类型为C的切片对象强制转换为CRTP<C>来调用未定义的行为,而不是。{/ p>

当您通过值传递时,您需要传递正确类型的值 - 对象实际所在的类型。对于任何C,您的对象不属于CRTP<C>类型,它们属于来自C派生类型(对于某些template <class C> typename std::enable_if<std::is_base_of<CRTP<C>, C>::value, Neg<C>>::type operator~ (C v) { return Neg<C>(std::move(v.self())); } )。

如果您想保留按值传递签名,您必须接受任何内容并使用SFINAE检查是否存在正确的基类:

{{1}}

或者你也可以使用完美的转发技巧。