How does this meta-programming compile down?
Here's an example from wikipedia that displays C++ template meta-programming:
template <int N>
struct Factorial
{
enum { value = 开发者_如何学GoN * Factorial<N - 1>::value };
};
template <>
struct Factorial<0>
{
enum { value = 1 };
};
// Factorial<4>::value == 24
// Factorial<0>::value == 1
void foo()
{
int x = Factorial<4>::value; // == 24
int y = Factorial<0>::value; // == 1
}
I understand how it works, recursively creating types of Factorial with template parameter values for N until the specialization is found <0>, which allows the compiler to resolve the values up the chain.
My question is: how would this look after compilation? Will the compiler literally end up just generating something like:
int x = 24;
int y = 1;
or will the result be more complex? I'm asking because I'm wondering if it basically results in:
Factorial<4>::value
being replaced by a constant (24) in the executable code or if its more complex than that. I'm just trying to figure out how this helps efficiency in the completed program, so this would help a lot :)
Yes, they are reduced to immediate compile-time values. A more telling example would be something like
int a[Factorial<4>::value];
or
struct S {
int a : Factorial<4>::value;
};
or
switch (a) {
case Factorial<4>::value: ;
}
i.e. contexts where constant compile-time values are required by the language. None of these examples would compile if Factorial<4>::value
wasn't an immediate compile-time constant.
Well, here's the generated assembly from GCC:
; command line used: gcc -c -S -fmasm-intel test.cpp
.file "test.cpp"
.intel_syntax noprefix
.text
.globl __Z3foov
.def __Z3foov; .scl 2; .type 32; .endef
__Z3foov:
LFB0:
push ebp
LCFI0:
mov ebp, esp
LCFI1:
sub esp, 16
LCFI2:
mov DWORD PTR [ebp-4], 24
mov DWORD PTR [ebp-8], 1
leave
LCFI3:
ret
LFE0:
And MSVC:
; command line used: cl -c /FAsc test.cpp
; Listing generated by Microsoft (R) Optimizing Compiler Version 16.00.30319.01
; removed soem excessive noise...
?foo@@YAXXZ PROC ; foo
; 16 : {
00000 55 push ebp
00001 8b ec mov ebp, esp
00003 83 ec 08 sub esp, 8
; 17 : int x = Factorial<4>::value; // == 24
00006 c7 45 f8 18 00
00 00 mov DWORD PTR _x$[ebp], 24 ; 00000018H
; 18 : int y = Factorial<0>::value; // == 1
0000d c7 45 fc 01 00
00 00 mov DWORD PTR _y$[ebp], 1
; 19 : }
00014 8b e5 mov esp, ebp
00016 5d pop ebp
00017 c3 ret 0
?foo@@YAXXZ ENDP ; foo
_TEXT ENDS
END
So the answer is that they boil the meta-programming down to its final result.
Note that I didn't even use any optimization flags.
Will the compiler literally end up just generating something like:
Yes. That's the whole point of doing something like that this way.
My question is: how would this look after compilation? Will the compiler literally end up just generating something like:
Yes. Factorial<3>::value
is actually a constant expression. That means, you can even write these:
const int M = Factorial<Factorial<3>::value>::value;
const int N = Factorial<Factorial<Factorial<3>::value>::value>::value;
And these too:
int array[Factorial<5>::value]; //its NOT a variable length array (VLA)
std::bitset<Factorial<5>::value> bits;
That is, wherever compile-time constant is required, you can use Factorial<5>::value
.
They are indeed compiled down to the former,
int x = 24;
int y = 1;
Though you'll find it difficult to come up with a real use for this pattern in actual production programs...
精彩评论