composer create-project drupal/recommended-project:~8.9.0 <dir>
Maybe we should change the template projects themselves to contain `~8.8.0`, `~8.9.0`, etc.; that way, if someone runs `composer create-project drupal/recommended-project:~8.8.0`, then they will get the latest `8.8.x` version of drupal/recommended-project, which will also also have `drupal/core-recommended:~8.8.0` as a dependency. If on the other hand they use `^8.8` instead of `~8.8.0` with `create-project`, then they will get the latest 8.x release of drupal/recommended-project, which will be 8.9.0 (if not today, imagine some point in the future when it will), which will have `drupal/core-recommended:~8.9.0`. This would be fairly consistent with how Composer is expected to behave.
The odd part is if someone tries to run `composer create-project drupal/recommended-project:8.7.5` in an attempt to specifically pin drupal/core-recommended to a specific version, then they will still get `drupal/core-recommended:~8.8.0` in their resulting project, because that’s what is in every 8.8.x version of the template.
Our other option would be to add a composer post-create-project script to fix up the composer.json of the resulting project after the fact (and then remove itself). However, I am not sure that it’s easy to get the constraint the user provided on the create-project commandline at post-create-project time, and in general I am wary about doing that much work to change the basic behavior of Composer. Perhaps we should be satisfied with what we can do with standard features and document the behavior.