constexpr
everything
Apart from the #define
, your code is quite good. But you miss on a really simple optimization by not using constexpr
, which has different meanings in different contexts:
constexpr variables
are compile-time constants. They're a good replacement for constant defining macros.
#define UPPER_BOUND 1000 // bad, because preprocessing leads to a lot of bugs
constexpr int upper_bound = 1000; // good, safe
constexpr functions
can be executed at compile-time if their arguments are known at compile-time; the compiler will then substitute a constant to the function call in the code.
So let's extract a function from your main()
and declare it constexpr
:
constexpr int sum_of_multiples_of_3_and_5_under(int upper_bound) {
int sum = 0;
for (int i = 1; i < upper_bound; ++i) {
if ( i % 3 == 0 || i % 5 == 0 ) {
sum += i;
}
}
return sum;
}
Now, whenever you call it with an upper_bound
you know at compile-time, it will run at compile-time.
Generalize your code
Running your function at compile-time means a lot, because if you can't and need your code to run fast at run-time, you'll have to implement better algorithms. As @Dannnno pointed out, you could have used the algorithm to compute an arithmetic progression, which is O(1)
instead of O(n)
for your code:
constexpr int sum_multiples_under(int ratio, int upper_bound) {
int nb_val = (upper_bound - 1) / ratio;
int high = nb_val * ratio;
int extremes = ratio + high;
return nb_val * extremes / 2;
}
You can then compute the sum of the multiples of 3 and 5 with:
auto sum = sum_multiples_under(3, 1000)
+ sum_multiples_under(5, 1000)
- sum_multiples_under(15, 1000);
It's quite easy to implement, even if it consumes a bit more brain-power than the for
-loop. But let's say that we want to generalize our code, and make it work for any number of divisors, for instance the sum of the multiples of 3, 5 and 7. Then it becomes a lot harder: you have to add all members of one set, then subtract all members of two sets, then add all members of three sets... etc. That means computing combinations, applying functions to arrangements, etc. Whereas our for
-loop scales quite comfortably.
Use fold expressions
With fold expressions (which require C++17), you can generalize your code fairly easily:
template <typename... Multiples>
constexpr int sum_of_multiples_under(int upper_bound, Multiples... multiples) {
int sum = 0;
for (int i = 1; i < upper_bound; ++i) {
if ( ( ... || (i % multiples == 0)) ) {
sum += i;
}
}
return sum;
}
Multiples
is a variadic template parameter. It can stand for any number of arguments. You can then expand the arguments' pack inside your function. Fold expressions allow you to expand it around an operator. In this case, the operator is ||
. ( ... || (i % multiples == 0))
will be expanded into:
(i % multiples_1 == 0)
|| (i % multiples_2 == 0)
|| ...
|| (i % multiples_N == 0)
Conclusion
constexpr
is a really, really useful keyword, and is a sign of modern, optimized C++ code. Use it without moderation. If you want to know everything about it, there's a keynote on YouTube named "constexpr everyting".