C++20 comes with super simple but really useful header, which is <numbers>!

What is <numbers>?

<numbers> is type safe way to access mathematical constants.
That's it. Let's take a look at:

#include <numbers> // std::numbers::pi_v
#include <iostream> // std::cout
#include <concepts> // std::floating_point
#include <iomanip> // std::setprecision

template<std::floating_point Fp>
constexpr Fp circumference_of_circle(Fp radius) noexcept {
  return static_cast<Fp>(2) * std::numbers::pi_v<Fp> * radius;
}

int main() {
  // circumference of a circle with radius of 2 as float
  // show only 10 digit
  std::cout << std::setprecision(10) << circumference_of_circle(2.f) << '\n';
  return 0;
}

As you can see, `_v` means `value`, so we are using `_v` suffix here to explicitly use `Fp` type; which can be float, double, long double, std::float16_t, etc. [basic.extended.fp]
we could use `pi` directly if our `Fp` is `double` [numbers.syn]; it's because `std::numbers::pi` is same as `std::numbers::pi_v<double>.

Without <numbers>, we fall back on platform specific C-style macros.
Which are generally not type safe, they just do some text manipulation and insert them into your code.
It's also hard to debug sometimes, so you're probably not going to see macro expansions quickly; unless using IDEs (CLion has good built-in plugin for this) or something like cppinsights.
Also, <cmath> header might require to define something like "_USE_MATH_DEFINES", depending on platform (Microsoft Learn).
That's because mathematical constants are not part of <cmath>, and why <numbers> makes sense at this point.

constants list (contents of std::numbers): [numbers]

It looks good, what if I do not have C++20 compliant compiler?

You might want to use templates for this kind of thing, you can define static constexpr variables on a (global) namespace.
But this sure do require at least C++17 compliant compiler.

Or you might want to use templates, then use `std::is_floating_point` from <type_traits>, you'll need C++11 or higher standard compliant compiler and STL.
And holding a strong belief in `std::enable_if`s.

Is using <cmath> *just for constants* considered as a bad practice if I have C++20 compliant compiler?

Yes. But I should emphasize that it's bad practice "if you have C++20 compliant compiler and STL". Since a lot of project still relies on ancient C++;
can't say it's really "bad practice", I can't blame them for not using that, there are bigger problems than this though. :)
Also, <cmath> is really good header. It will bring us more (already have some [cmath.syn]) `constexpr` math functions with C++26; also it's regularly updated with new functions.

I have no source for this but probably using <numbers> instead of <cmath> *just for constants* would probably reduce binary size enough if you are not using modules.

So, why should I use that header?

Notes:

* Used C++23 N4950 final working draft.

* If there is any wrong information or something can be added extra, please go to repository, and create new pull request. Thank you!