小さい算術クラスでのExpression Template

うにゅぅ.id:ototoiさんのところで指摘されてた現象(id:ototoi:20041126#p1)が再現しない・・・.

#include <cstddef>

template<class LHS, class RHS>
class vector_plus
{
public:
  vector_plus(LHS const &lhs, RHS const &rhs) :
    lhs_(lhs),
    rhs_(rhs)
  {
  }

  double operator[](std::size_t idx) const
  {
    return lhs_[idx] + rhs_[idx];
  }

private:
  LHS const &lhs_;
  RHS const &rhs_;
};

// trivially assignableなのでコピーコンストラクタと
// 代入演算子の生成はコンパイラ任せ
class vector
{
public:
  vector() :
    val_()
  {
  }

  template<class T>
  vector(T const &r) :
    val_()
  {
    val_[0] = r[0];
    val_[1] = r[1];
    val_[2] = r[2];
  }

  template<class T>
  vector &operator=(T const &r)
  {
    val_[0] = r[0];
    val_[1] = r[1];
    val_[2] = r[2];

    return *this;
  }

  double &operator[](std::size_t idx)
  {
    return val_[idx];
  }

  double const &operator[](std::size_t idx) const
  {
    return val_[idx];
  }

private:
  double val_[3];
};

template<class RHS, class LHS>
inline
vector_plus<RHS, LHS>
operator+(RHS const &rhs, LHS const &lhs)
{
  return vector_plus<RHS, LHS>(rhs, lhs);
}

void f(vector &out, vector const &u, vector const &v, vector const &w)
{
  out = u + v + w;
}

// 比較のベースラインとして用意した関数.fと処理内容一緒.
void g(double *out, double const *p, double const *q, double const *r)
{
  out[0] = p[0] + q[0] + r[0];
  out[1] = p[1] + q[1] + r[1];
  out[2] = p[2] + q[2] + r[2];
}

上のコードをVC++7.1 /Oxでコンパイルしたら以下のようになりました.

fのアセンブリgのアセンブリ(ベースライン)

void f(vector &out, vector const &u, vector const &v, vector const &w)
{
00401070 sub esp,8
out = u + v + w;
00401073 mov eax,dword ptr [esp+10h]
00401077 mov ecx,dword ptr [esp+14h]
0040107B mov edx,dword ptr [esp+18h]
0040107F mov dword ptr [esp],eax
00401082 mov dword ptr [esp+4],ecx
00401086 fld qword ptr [eax]
00401088 fadd qword ptr [ecx]
0040108A push esi
0040108B mov esi,dword ptr [esp+10h]
0040108F fadd qword ptr [edx]
00401091 fstp qword ptr [esi]
00401093 fld qword ptr [eax+8]
00401096 fadd qword ptr [ecx+8]
00401099 fadd qword ptr [edx+8]
0040109C fstp qword ptr [esi+8]
0040109F fld qword ptr [eax+10h]
004010A2 fadd qword ptr [ecx+10h]
004010A5 fadd qword ptr [edx+10h]
004010A8 fstp qword ptr [esi+10h]
004010AB pop esi
}
004010AC add esp,8
004010AF ret

void g(double *out, double const *p, double const *q, double const *r)
{
out[0] = p[0] + q[0] + r[0];
00401030 mov eax,dword ptr [esp+8]
00401034 fld qword ptr [eax]
00401036 mov ecx,dword ptr [esp+0Ch]
0040103A fadd qword ptr [ecx]
0040103C mov edx,dword ptr [esp+10h]
00401040 push esi
00401041 mov esi,dword ptr [esp+8]
00401045 fadd qword ptr [edx]
00401047 fstp qword ptr [esi]
out[1] = p[1] + q[1] + r[1];
00401049 fld qword ptr [eax+8]
0040104C fadd qword ptr [ecx+8]
0040104F fadd qword ptr [edx+8]
00401052 fstp qword ptr [esi+8]
out[2] = p[2] + q[2] + r[2];
00401055 fld qword ptr [eax+10h]
00401058 fadd qword ptr [ecx+10h]
0040105B fadd qword ptr [edx+10h]
0040105E fstp qword ptr [esi+10h]
00401061 pop esi
}
00401062 ret
GCC 3.2.2でのコンパイルの結果は以下になりました.
gのアセンブリ with -O2
-O3も同じ
(ベースライン)
fのアセンブリ with -O2 fのアセンブリ with -O3

pushl %ebp
movl %esp, %ebp
movl 16(%ebp), %eax
pushl %ebx
fldl (%eax)
movl 12(%ebp), %ecx
faddl (%ecx)
movl 20(%ebp), %edx
movl 8(%ebp), %ebx
faddl (%edx)
fstpl (%ebx)
fldl 8(%eax)
faddl 8(%ecx)
faddl 8(%edx)
fstpl 8(%ebx)
fldl 16(%eax)
faddl 16(%ecx)
faddl 16(%edx)
fstpl 16(%ebx)
movl (%esp), %ebx
leave
ret

pushl %ebp
movl %esp, %ebp
pushl %esi
pushl %ebx
subl $16, %esp
movl 12(%ebp), %ebx
movl 16(%ebp), %edx
movl 20(%ebp), %ecx
leal -16(%ebp), %eax
movl %ebx, -16(%ebp)
movl %edx, -12(%ebp)
movl %eax, -24(%ebp)
movl %ecx, -20(%ebp)
fldl (%edx)
faddl (%ebx)
movl 8(%ebp), %esi
faddl (%ecx)
fstpl (%esi)
fldl 8(%edx)
faddl 8(%ebx)
faddl 8(%ecx)
fstpl 8(%esi)
fldl 16(%edx)
faddl 16(%ebx)
faddl 16(%ecx)
fstpl 16(%esi)
addl $16, %esp
popl %ebx
popl %esi
leave
ret

movl 12(%ebp), %ebx
movl 16(%ebp), %edx
movl 20(%ebp), %ecx
leal -16(%ebp), %eax
movl %ebx, -16(%ebp)
movl %edx, -12(%ebp)
movl %eax, -24(%ebp)
movl %ecx, -20(%ebp)
fldl (%edx)
faddl (%ebx)
movl 8(%ebp), %esi
faddl (%ecx)
fstpl (%esi)
fldl 8(%edx)
faddl 8(%ebx)
faddl 8(%ecx)
fstpl 8(%esi)
fldl 16(%edx)
faddl 16(%ebx)
faddl 16(%ecx)
fstpl 16(%esi)
addl $16, %esp
popl %ebx
popl %esi
leave
ret
うむぅ,再現しませんでした・・・.原因として考えられるのはvectorクラスの実装の違いぐらいなんですが・・・.
あと,式オブジェクトのobject generatorのoperator+のパラメータを値渡しにしちゃうと,CRTPによるオーバーロードの限定が(slicingするので)出来なくなるのはもちろん,「副作用のあるコンストラクタ・デストラクタを持った名前付きの自動変数は,たとえそれが使われないとしても最適化で削除しちゃダメよ.」という制限(規格の(3.7.2/1),これについてはExceptional C++の項目46やMC++Dの5.12にもちょっとだけ記述があります)のためにコンパイラvectorオブジェクトと式オブジェクトを削除してくれなくなっちゃいます.
例えば上のコードでoperator+の定義を

template<class RHS, class LHS>
inline
vector_plus<RHS, LHS>
operator+(RHS rhs, LHS lhs)
{
  return vector_plus<RHS, LHS>(rhs, lhs);
}

と変えただけで以下のようになっちゃいました.(他はさっきと全く同じ条件)

// VC++7.1 with /Oxでのfのアセンブリ
void f(vector &out, vector const &u, vector const &v, vector const &w)
{
00401070  sub         esp,50h 
  out = u + v + w;
00401073  mov         eax,dword ptr [esp+5Ch] 
00401077  mov         ecx,dword ptr [eax] 
00401079  mov         edx,dword ptr [eax+4] 
0040107C  mov         dword ptr [esp+3Ch],edx 
00401080  mov         dword ptr [esp+38h],ecx 
00401084  mov         ecx,dword ptr [eax+8] 
00401087  mov         edx,dword ptr [eax+0Ch] 
0040108A  mov         dword ptr [esp+44h],edx 
0040108E  mov         dword ptr [esp+40h],ecx 
00401092  mov         ecx,dword ptr [eax+10h] 
00401095  mov         edx,dword ptr [eax+14h] 
00401098  mov         eax,dword ptr [esp+58h] 
0040109C  mov         dword ptr [esp+4Ch],edx 
004010A0  mov         dword ptr [esp+48h],ecx 
004010A4  mov         ecx,dword ptr [eax] 
004010A6  mov         edx,dword ptr [eax+4] 
004010A9  mov         dword ptr [esp+24h],edx 
004010AD  mov         dword ptr [esp+20h],ecx 
004010B1  mov         ecx,dword ptr [eax+8] 
004010B4  mov         edx,dword ptr [eax+0Ch] 
004010B7  mov         dword ptr [esp+2Ch],edx 
004010BB  mov         dword ptr [esp+28h],ecx 
004010BF  mov         ecx,dword ptr [eax+10h] 
004010C2  mov         edx,dword ptr [eax+14h] 
004010C5  mov         dword ptr [esp+34h],edx 
004010C9  mov         edx,dword ptr [esp+60h] 
004010CD  push        esi  
004010CE  mov         esi,dword ptr [edx] 
004010D0  mov         dword ptr [esp+0Ch],esi 
004010D4  mov         esi,dword ptr [edx+4] 
004010D7  mov         dword ptr [esp+10h],esi 
004010DB  mov         esi,dword ptr [edx+8] 
004010DE  mov         dword ptr [esp+14h],esi 
004010E2  mov         esi,dword ptr [edx+0Ch] 
004010E5  mov         dword ptr [esp+18h],esi 
004010E9  mov         esi,dword ptr [edx+10h] 
004010EC  mov         edx,dword ptr [edx+14h] 
004010EF  mov         dword ptr [esp+34h],ecx 
004010F3  mov         dword ptr [esp+1Ch],esi 
004010F7  mov         dword ptr [esp+20h],edx 
004010FB  mov         edx,dword ptr [esp+58h] 
004010FF  lea         eax,[esp+24h] 
00401103  lea         ecx,[esp+3Ch] 
00401107  mov         dword ptr [esp+4],eax 
0040110B  mov         dword ptr [esp+8],ecx 
0040110F  fld         qword ptr [ecx] 
00401111  fadd        qword ptr [eax] 
00401113  pop         esi  
00401114  fadd        qword ptr [esp+8] 
00401118  fstp        qword ptr [edx] 
0040111A  fld         qword ptr [ecx+8] 
0040111D  fadd        qword ptr [eax+8] 
00401120  fadd        qword ptr [esp+10h] 
00401124  fstp        qword ptr [edx+8] 
00401127  fld         qword ptr [ecx+10h] 
0040112A  fadd        qword ptr [eax+10h] 
0040112D  fadd        qword ptr [esp+18h] 
00401131  fstp        qword ptr [edx+10h] 
}
00401134  add         esp,50h 
00401137  ret              

GCCでも同様.

// GCC 3.2.2 with -O3でのfのアセンブリ
	pushl	%ebp
	movl	%esp, %ebp
	pushl	%edi
	pushl	%esi
	pushl	%ebx
	subl	$140, %esp
	movl	12(%ebp), %esi
	cld
	leal	-56(%ebp), %edi
	movl	$6, %ecx
	rep
	movsl
	movl	16(%ebp), %esi
	leal	-88(%ebp), %edi
	movl	$6, %ecx
	rep
	movsl
	leal	-56(%ebp), %esi
	leal	-88(%ebp), %ecx
	movl	%esi, -128(%ebp)
	movl	%ecx, -124(%ebp)
	movl	20(%ebp), %esi
	leal	-120(%ebp), %edi
	movl	$6, %ecx
	rep
	movsl
	fldl	-88(%ebp)
	faddl	-56(%ebp)
	movl	8(%ebp), %ebx
	faddl	-120(%ebp)
	fstpl	(%ebx)
	fldl	-80(%ebp)
	faddl	-48(%ebp)
	faddl	-112(%ebp)
	fstpl	8(%ebx)
	fldl	-72(%ebp)
	faddl	-40(%ebp)
	faddl	-104(%ebp)
	leal	-120(%ebp), %ecx
	leal	-128(%ebp), %edx
	fstpl	16(%ebx)
	movl	%edx, -136(%ebp)
	movl	%ecx, -132(%ebp)
	addl	$140, %esp
	popl	%ebx
	popl	%esi
	popl	%edi
	leave
	ret

というか,vectorクラスの実装の違いしかないはずなのに何でここまで結果が変わってくるんだろう?
#ありゃ.式オブジェクトが保持しているconst参照を値にしたらやっぱりオブジェクトの削除をしてくれなくなっちゃった.もしかしてメンバ変数も「副作用のあるコンストラクタ・デストラクタを持った名前付きの自動変数」に該当するのか?だとすると式オブジェクトは(const)参照持つ以外にやりようがないんじゃ・・・.
#ちぅかメンバ変数のstorage durationってどうなるのよ?
#っていうか,

3.7.4 Duration of sub-objects
1 The storage duration of member subobjects, base class subobjects and array elements is that of their complete object (1.8).

そうか・・・なら(const)参照で持つ以外に方法がないな・・・ってΣ(゜Д゜;エーッ!?
#Expression Templateの際に中間的に生成される式オブジェクトのうち,以下のいずれかに該当するオブジェクトを最適化で削除することは規格で禁止されているため,そのようなオブジェクトの削除は絶対にありえない.

  1. 式オブジェクトを生成する関数のパラメータでctor/dtorに副作用のある型のオブジェクト(普通Restricted Name Injectionのために参照にするのでこれは自動的に外れる)
  2. 式オブジェクトが保持するデータメンバで,ctor/dtorに副作用のある型のオブジェクト
  3. (N)RVOが有効でない形で生成された式オブジェクト

ただし,上のどの条件にも当てはまらないオブジェクトが最適化によって削除されるかどうかはコンパイラ次第である.上の条件はあくまで「上の条件に該当するオブジェクト->削除されない」であって「上の条件に該当しない->削除される」ではない.
言語規格に厳密に従うとこういう結論になるんですけど・・・ホンマか,これ!?
#上の2番目を簡単に言うと,「式オブジェクトが値で式オブジェクトを持つと,その式オブジェクトが最適化で削除されることはない」になる.
#いやマテマテ.sub-objectのcomplete-objectにRVOが適応される場合はどないやねん!?
#ぁぁぁ・・・operator[]で使うんだった・・・もうダメポ・・・
#っていうかそもそも"side effect"ってどこまでの範囲を指してるのよ?
#うがー!!
#こりゃもうcomp.lang.c++.moderatedあたりに投げるしかないのか?