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: templateT* 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 右辺値参照