`CMAKE_<LANG>_STANDARD` and FetchContent/Cache variable interaction

I have a question about how CMAKE_<LANG>_STANDARD and which takes precedence.

  • Cache variable -DCMAKE_<LANG>_STANDARD vs set() (not cached):
    • Local variable wins because it shadows cached variable
  • set() in parent project vs set() in FetchContented project:
    • FetchContent definition wins
  • set_target_properties vs any value of CMAKE_<LANG>_STANDARD:
    • Property wins

So then what is and appropriate design:

  • Always use set() (uncached) to set the project’s minimum standard, allowing the user to bump/decrease the standard version. User cannot overwrite project’s standard
  • It is the importer’s responsibility to make sure the imported project satisfies their minimum version requirement. This is to avoid confusion like: Project’s standard is always respected even if parent project has different standard
  • How to set a recommended minimum STANDARD (e.g. C++23), and if compiler does not support it, default to a minimum standard that is supported (e.g. C++17)?

Am I missing some nuances in this?

Local variables should always shadow cache variables.

The more-local scope will win here (so the internal FetchContent project).

All the variable does is initialize the target property, so the property will win.

Well not if they are read during the build process right? But I did try this and it did not get shadowed to my surprise. I’ll update with an example.

If you mean “during the build process” something that tries to peek into CMakeCache.txt and divine something…sure the cache is the only thing that exists at that point. An example would help a lot.

I have tried to replicate the environment here, from an earlier experiment, but it seems I cannot replicate. I might have not had the CMAKE_C_STANDARD set in that commit so it only took the value from the cache.

So in this case the values prescribed in the project always win. Then my confusion is resolved except for:

Advice on that? How do I test the compiler feature? Do I have to do it on individual targets as:

target_compile_features(mylib PUBLIC
    "$<$<COMPILE_FEATURES:cxx_std_23>:cxx_std_23>"
    "$<$<NOT:$<COMPILE_FEATURES:cxx_std_23>>:cxx_std_17>"
)

Is there an easier approach with global variable?

I don’t think there’s an easier way to do it, no. However, I think your genex does nothing useful as it will only say cxx_std_23 if the linking target already has cxx_std_23 available. I’d just use cxx_std_17 and use set(CMAKE_CXX_STANDARD 23) when you want to test C++23.

Hmm, that’s confusing. My understanding of that genex is that it tests the compiler for C++23 and if available it sets that as the standard, otherwise it sets it as C++17. The difference with set(CMAKE_CXX_STANDARD 23) is that if it’s not satisfied, it does not set an appropriate minimum standard. Or am I wrong and cmake goes through the standard list and it picks the first highest one that is satisfied?

Based on my reading of the docs:

where features is a comma-separated list. Evaluates to 1 if all of the features are available for the ‘head’ target, and 0 otherwise.

the “available to the ‘head’ target” means “has in its compile feature list”.

If this expression is used while evaluating the link implementation of a target and if any dependency transitively increases the required C_STANDARD or CXX_STANDARD for the ‘head’ target, an error is reported.

Means that increasing the standard while doing the query is going to raise an error.

So, basically: try it out and see if it works (my reading might indeed be inaccurate). Whatever the results, the docs could probably use some clarification.