Last time i was experimenting with what constexpr has to offer for compile time calculations & ran into limits with how nested these can be. However back then with C++11 these could have only 1 return statement and thus were good but anything fancy would have resulted in convulated implementation.
With much better C++17 compiler support quite a few of the template metaprograms can easily be moved to constexpr
. Esp the implementation from here and here becomes just a few functions rather than #n types
.
factorial is as simple as (but still have just 1 return statement, so C++11 be fine):
1
2
3
4
| constexpr int factorial (int num)
{
return num < 2 ? 1 : num * factorial (num - 1);
}
|
instead of
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
| template<int Num>
struct factorial_c
{
enum
{
value = Num * factorial_c<Num-1>::value
};
};
// terminating conditions
// factorial of 1 is 1
template<>
struct factorial_c<1>
{
enum
{
value = 1
};
};
// factorial of 0 is 1
template<>
struct factorial_c<0>
{
enum
{
value = 1
};
};
|
But the biggest difference is with C++17:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| int minimum (int a, int b)
{
return a < b ? a : b;
}
int maximum (int a, int b)
{
return a > b ? a : b;
}
int gcd_recursive(int a, int b)
{
if( a == b ) return a;
if( minimum(a,b) == 0 ) return maximum(a,b);
return gcd_recursive(minimum (a, b), maximum (a, b) % minimum (a, b));
}
|
becomes:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| constexpr int minimum (int a, int b)
{
return a < b ? a : b;
}
constexpr int maximum (int a, int b)
{
return a > b ? a : b;
}
constexpr int gcd (int a, int b)
{
if (a == b) return a;
if (minimum (a, b) == 0) return maximum (a, b);
return gcd (minimum (a, b), maximum (a, b) % minimum (a, b));
}
|
instead of:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
| template<int One, int Two>
struct max_c
{
enum
{
value = One > Two ? One : Two
};
};
template<int One, int Two>
struct min_c
{
enum
{
value = One < Two ? One : Two
};
};
template<int One, int Two>
struct gcd_c
{
enum
{
// equivalent to: `value = gcd(min, max % min)`
value = gcd_c< min_c<One, Two>::value,
max_c<One, Two>::value %
min_c<One,Two>::value>::value
};
};
// template specialization for Min being 0, this is our
// termination condition for recursion
template<int Max>
struct gcd_c<Max, 0>
{
enum
{
value = Max
};
};
|
And these are all computed at compile-time as is evident from the program here
1
2
3
4
5
6
7
8
9
10
11
12
| int main ()
{
constexpr auto fac5 = factorial (5);
constexpr auto gcdval = gcd (17 * 17, 17 * 16);
static_assert(gcdval == 17, "incorrect gcd value");
constexpr auto lcmval = lcm(17*17, 17*16);
static_assert(lcmval == 4624, "incorrect lcm value");
return 0;
}
|
generated assembly:
1
2
3
4
5
6
7
8
9
10
11
12
13
| fac5$ = 0
gcdval$ = 4
lcmval$ = 8
main PROC
$LN3:
sub rsp, 24
mov DWORD PTR fac5$[rsp], 120 ; 00000078H
mov DWORD PTR gcdval$[rsp], 17
mov DWORD PTR lcmval$[rsp], 4624 ; 00001210H
xor eax, eax
add rsp, 24
ret 0
main ENDP
|