Visual C++ Compiler Nov 2012 CTP では動いていたけど、
VisualStudio 2013 以降コンパイルできなくなったコード。
struct List {
int var_a, var_b;
List( int a, int b ) : var_a( a ), var_b( b )
{
}
};
class Tree {
public:
template
T* Alloc( A0... a0 )
{
return new T( a0... );
}
};
class Compiler {
Tree tree;
public:
template
T* Alloc( A0... a0 )
{
return tree.Alloc( a0... );
}
};
...
Compiler compiler;
compiler.Alloc( 1, 2 );
VS2012 + Nov 2012 CTP : OK VS2013 : internal error VS2013 + Nov 2013 CTP : internal error gcc 4.8.1 : OK clang 3.2 : OK
template を分解して単純化して回避。
struct T0 {
T0()= default;
T0( const T0& )= default;
T0( int a ){};
};
↑ T0 は VS ではまだ POD 扱いにならないようです。
std::is_pod
-----------------------------------
VS2013 false
VS2013 + Nov 2013 CTP false
gcc 4.8.1 (Linux) true
clang 3.2 (Linux) true
POD かどうかで、値渡し時に関数の呼び出し方が変わります。
sizeof が 8byte 未満でも register に入らず参照戻しになります。
struct T1 {
T1()= default;
T1( const T1& )= default;
int var;
};
struct T2 {
T2()= default;
T2( const T2& )= default;
T2( int a ){};
int var;
};
T1 pod_test1( T1 a )
{
return a;
}
T2 pod_test2( T2 a )
{
return a;
}
void pod_test()
{
T1 a1;
T1 ret1= pod_test1( a1 );
T2 a2;
T2 ret2= pod_test2( a2 );
}
// Windows x64
// T1
xor ecx, ecx
call ?pod_test1@@YA?AUT1@@U1@@Z ; pod_test1
// T2
lea rcx, QWORD PTR $T2[rsp]
xor edx, edx
call ?pod_test2@@YA?AUT2@@U1@@Z ; pod_test2
Windows x64 は fastcall 相当なので引数はレジスタ ecx, edx, r8, r9 に入ります。
上の T1 ではそのまま引数が ecx に入っています。
↑ T2 の場合戻り値を受け取るために、呼び出し側が領域を確保して rcx で渡しています。
実際の引数は edx です。
// Windows x64
// T1
?pod_test1@@YA?AUT1@@U1@@Z PROC ; pod_test1, COMDAT
mov eax, ecx
ret 0
// T2
?pod_test2@@YA?AUT2@@U1@@Z PROC ; pod_test2, COMDAT
mov DWORD PTR [rcx], edx
mov rax, rcx
ret 0
↑ T1 では呼ばれた側は引数を戻り値 rax にコピーしているだけ。
↑ T2 は rcx のバッファに結果を格納しつつ rax にもアドレスを返しています。
gcc 4.8/clang 3.2 ではどちらも POD 扱いなので T1/T2 共に同じコードとなっています。
Linux では呼び出し規約 (calling convention) が異なるのでレジスタは別です。
(rdi, rsi, rdx, rcx, r8, r9 の順)
// Linux x86_x64
// T1
xorl %edi, %edi
callq _Z9pod_test12T1
// T2
xorl %edi, %edi
callq _Z9pod_test22T2
// Linux x86_x64
// T1
_Z9pod_test12T1: # @_Z9pod_test12T1
movl %edi, %eax
ret
// T2
_Z9pod_test22T2: # @_Z9pod_test22T2
movl %edi, %eax
ret
下記は gcc 以外はコンパイルが通ります。
templatevoid CallScript_UIEvent( A0... a0 ) { slice::Add( "Move", [=]( slice::SliceBase* slice ){ CallScript_Direct( a0... ); slice->Delete(); } ); }
VS2012 + Nov 2012 CTP : OK VS2013 : OK VS2013 + Nov 2013 CTP : OK gcc 4.8.1 (Linux) : error clang 3.2 (Linux) : OK
template 引数が可変長でなければコンパイル通ります。
関連エントリ
・C++11 Lambda function ラムダ関数
・C++11 Variadic Templates 可変長引数のテンプレート
・スレッド同期命令の比較 C++11 とコンパイラ
・C++11 Rvalue Reference 右辺値参照