Expression Template と auto
これはC++ Advent Calendar jp 2010への参加記事です。
C++の次世代規格であるC++0xには、新しくautoという機能が加わります。autoは次のように使います。
auto p = std::make_pair(1, 2.0);
上のコードでは、std::make_pair(1, 2.0)の型が推論され、pの型はstd::pair
以下のような
std::pair<int, double> p = std::make_pair(1, 2.0);
冗長な型の記述をなくせる非常に便利な機能です。constをつけたり、参照にしたりすることもできます。詳しくはこちらを御覧ください。
このようにautoはとても便利な機能なので、C++0xが普及したら多用されることでしょう。しかし、何も考えずにautoを使うと、ごく稀に分かりにくいバグを入れてしまう場合があります。Expression Templateを使ったライブラリを使用する場合です。
Expression Template
Expression Templateとは、式をテンプレートの階層として表現することにより、様々な機能を実現する手法です。Boost.uBLASのような線形代数ライブラリ、Boost.LambdaやBoost.Spiritなどに用いられています。次のような、簡単なベクトルを表すクラステンプレートを考えます。
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_); } Vector& operator=(Vector const& v) { Vector tmp(v); swap(tmp); return *this; } 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> Vector<T> operator+(Vector<T> const& u, Vector<T> const& v) { std::size_t size = u.size(); assert(size == v.size()); Vector<T> result(size); for (std::size_t i = 0; i < size; ++i) result[i] = u[i] + v[i]; return result; }
を考えます。Vector
Vector<double> u = v1 + v2 + v3;
と書きます。ここでは、v1 + v2が実行されて一時オブジェクトを返し、その一時オブジェクトとv3との和によってuが初期化されます。v1 + v2によって無駄な一時オブジェクトが生成されています。Expression Templateを使うと、シンプルな記述を保ちながら一時オブジェクトの生成を避けることができます。
上の加法演算子の代わりに、次のようなPlusクラステンプレート
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>)); Plus(Vec1 const& u, Vec2 const& v) : lhs_(u), rhs_(v) {} 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) { 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: 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); }
を定義します。この2つの定義によって、以下の文
Vector<double> u = v1 + v2 + v3;
においてv1 + v2ではVectorautoを使う
型をVector
auto u = v1 + v2 + v3;
このとき、uの型はVector
auto u = Vector<double>(3) + Vector<double>(3);
などとすると、動作未定義への道が開かれます。
補足を書きました。