Use variables or genexes with usage requirements?

Hi all, I have a question about setting usage requirements which depend on platform or compiler. Assume I have a C++ library Foo such that:

  • When consumers link against it on Windows, they must define FOO_WINDOWS.
  • When the consumer is built using GCC or Clang, -fpersmissive must be used.

(The above macro/flag are just examples, the question holds for arbitrary macro/option usage requirements).

I want to express these as usage requirements on Foo. My question is, what are the [dis]advantages of using variable-conditioned settings vs. generator expressions? Basically, should I do this:

if(WIN32)
  target_compile_definitions(Foo INTERFACE FOO_WINDOWS)
endif()
if(CMAKE_CXX_COMPILER_ID MATCHES GNU|Clang)
  target_compile_options(Foo INTERFACE -fpermissive)
endif()

or this:

target_compile_definitions(Foo INTERFACE $<$<PLATFORM_ID:Windows>:FOO_WINDOWS>)
target_compile_options(Foo INTERFACE $<$<OR:$<CXX_COMPILER_ID:GNU>,$<CXX_COMPILER_ID:Clang>>:-fpermissive>)

My intuition is to use the former (variable-based) approach, since it’s easier to read and I expect it to be faster as well, as it will only be evaluated once, not once per build configuration. (This could add up if dozens of such libraries are involved, each with its own slightly different set of usage requirements).

I understand the advantage of the genex-based approach in situations such as using CUDA, where different compilers can get their hands on the same file. However, when such special cases are not involved and the buildsystem follows CMake’s normal limitation of “one platform, one toolchain”, is there any advantage to one or the other approach?

Also, does the answer change if I were not defining Foo as part of the build, but writing a package config file for it which could be consumed by the outside world?

For platform-specific logic, either choice works, but variables are probably clearer. For compiler-specific logic, the compiler you build with might not be the same as the one the consumer is using, so that should probably be done through generator expressions if using different compilers like that is acceptable.

I think I’d still prefer the genex approach myself. But rarely do I have platform-specific things like that in CMake (as I believe that is usually done in headers in the projects I’ve worked on). Compiler-specific things can’t be in headers and have to be in the usage interface.

Thanks everyone for the answers.

That is something I failed to fully consider for the package case, and is a very good point.