Expression Template と auto 追補

このエントリは Expression Template と auto の補足です。

先日は問題の提示だけして解決法を書きませんでした。本エントリでは解決法と、Expression Templateで実装されたライブラリを使うときの注意と、前回のサンプルコードについて述べます。

解決法

問題は

Vector<double> v1(3), v2(3);
auto v3 = v1 + v2;

とするとv3の型がPlus, Vector >に推論され、これはv1とv2の値ではなく参照を持つので危険だということでした。autoで加法演算子の結果を受けられないようにすれば問題は解決します。

そのために、Plus<...>のコピーコンストラクタをprivateにすれば、autoで受けられなくなります。

template <typename Vec1, typename Vec2>
class Plus {
  // ...
private:
  Plus(Plus const&);
  // ...
};

コピーコンストラクタをprivateにすると、Plus<...>に推論されたv3のコピーコンストラクタが不可視になります。また、コピーコンストラクタを宣言するとムーブコンストラクタが暗黙に生成されないので、v3を構築することができなくなります。

コピーコンストラクタをprivateにしたために、operator+がPlus<...>を返すことができなくなりますが、operator+をPlus<...>のfriendにすれば返せるようになります。

template <typename Vec1, typename Vec2>
class Plus {
  // ...
  template <typename U1, typename U2>
  Plus<U1, U2> operator+(U1 const&, U2 const&);
  // ...
};

Expression Templateで実装されたライブラリを使うときの注意

まず使用するライブラリがExpression Templateで実装されているかを確認しましょう。std::valarrayがExpression Templateで実装されているかもしれないということはあまり知られていないと思います。C++の規格では(C++03でもC++0xでも)std::valarrayをExpression Templateで実装してもしなくても構わないことになっています。

そしてExpression Templateで実装されている可能性があることが分かったら、autoで演算結果を受けないように注意しましょう。既存のExpression Templateは上のような対応はしていないことが多いので気をつけるしかありません。

サンプルコード

前回のサンプルコードはインクルードするヘッダを書かない、すべてをグローバル名前空間に置く、などの問題がありましたので修正したものを以下に貼ります。

#include <cassert>
#include <algorithm>
#include <boost/config.hpp>
#include <boost/mpl/assert.hpp>
#include <boost/type_traits/is_same.hpp>
#include <boost/scoped_array.hpp>
 
namespace pepshiso {
namespace impl {
template <typename T>
class Vector {
public:
  typedef T value_type;

  explicit Vector(std::size_t n)
    : buf_(new T[n]), size_(n)
  {}
  Vector(Vector const& v)
    : buf_(new T[v.size_]), size_(v.size_)
  {
    std::copy(v.buf_, v.buf_ + v.size_, buf_);
  }
#ifndef BOOST_NO_RVALUE_REFERENCES
  Vector(Vector&& v)
    : size_(0)
  {
    swap(v);
  }
#endif
  Vector& operator=(Vector const& v) {
    Vector tmp(v);
    swap(tmp);
    return *this;
  }
#ifndef BOOST_NO_RVALUE_REFERENCES
  Vector& operator=(Vector&& v) {
    swap(v);
    return *this;
  }
#endif

  void swap(Vector& v) {
    using std::swap;
    swap(buf_, v.buf_);
    swap(size_, v.size_);
  }
 
  T& operator[](std::size_t n) {
    return const_cast<T&>(const_cast<Vector const&>(*this)[n]);
  }
  T const& operator[](std::size_t n) const {
    assert(n < size_);
    return buf_[n];
  }
 
  std::size_t size() const { return size_; }
 
private:
  boost::scoped_array<T> buf_;
  std::size_t size_;
};

template <typename T>
void swap(Vector<T>& u, Vector<T>& v)
{
  u.swap(v);
}
 
template <typename Vec1, typename Vec2>
class Plus {
public:
  typedef typename Vec1::value_type value_type;
  BOOST_MPL_ASSERT((boost::is_same<value_type, typename Vec2::value_type>));

  template <typename U1, typename U2>
  friend
  Plus<U1, U2> operator+(U1 const&, U2 const&);

  std::size_t size() const {
    std::size_t const size = lhs_.size();
    assert(size == rhs_.size());
    return size;
  }
 
  value_type operator[](std::size_t n) const {
    assert(n < size());
    return lhs_[n] + rhs_[n];
  }
 
  operator Vector<value_type>() const {
    Vector<value_type> result(size());
    for (std::size_t i = 0, sz = size(); i < sz; ++i)
      result[i] = lhs_[i] + rhs_[i];
    return result;
  }

private:
  Plus(Vec1 const& u, Vec2 const& v)
    : lhs_(u), rhs_(v)
  {}
  Plus(Plus const& other)
    : lhs_(other.lhs_), rhs_(other.rhs_)
  {}
 
  Vec1 const& lhs_;
  Vec2 const& rhs_;
};

template <typename Vec1, typename Vec2>
Plus<Vec1, Vec2> operator+(Vec1 const& u, Vec2 const& v)
{
  return Plus<Vec1, Vec2>(u, v);
}
} // namespace impl
 
using impl::Vector;
 
} // namespace pepshiso
 

int main()
{
  using namespace pepshiso;
  Vector<double> v1(3), v2(3);
  v1[0] = 1, v1[1] = 2, v1[2] = 3;
  v2[0] = 10, v2[1] = 20, v2[2] = 30;

  // ok
  Vector<double> v3 = v1 + v2;

  // bad
  // auto v4 = v1 + v2;
}