# A Hater's Case Against Config.hpp

**URL:** <https://discourse.bemanproject.org/t/a-haters-case-against-config-hpp/434>\
**Category:** Beman Project Development\
**Created:** [June 6, 2025, 7:59pm UTC](https://discourse.bemanproject.org/t/a-haters-case-against-config-hpp/434 "2025-06-06T19:59:09Z")\
**Posts on this page:** 4\
**Page:** 1

<div class="post-metadata">

**Author:** ![vito.gamberini](https://yyz1.discourse-cdn.com/flex029/user_avatar/discourse.bemanproject.org/vito.gamberini/32/261_2.png) [@vito.gamberini](https://discourse.bemanproject.org/u/vito.gamberini)\
**Post date:** [June 6, 2025, 7:59pm UTC](https://discourse.bemanproject.org/t/a-haters-case-against-config-hpp/434/1 "2025-06-06T19:59:09Z")

</div>

[**[CMAKE.PASSIVE\_TARGETS]**](https://github.com/bemanproject/beman/blob/main/docs/BEMAN_STANDARD.md#cmakepassive_targets) states:

> Preprocessor definitions intended for external use should be generated into a `config.hpp` file at CMake configuration time. This `config.hpp` should then be included by public headers.

And [**[CPP.NO\_FLAG\_FORKING]**](https://github.com/bemanproject/beman/blob/main/docs/BEMAN_STANDARD.md#cppno_flag_forking) reiterates:

> 1. Check for availability at CMake time using, for example, `check_cxx_source_compiles`.
> 2. Create a CMake `option` (e.g. `BEMAN_<short_name>_USE_DEDUCING_THIS`) with a default value based on detected support.
> 3. Generate a `config.hpp` with a `#define` macro set to the selected option.
> 4. Use this macro in place of the feature test macro.

I believe this is wrong. I am a hater of `config.hpp`-style solutions to package configuration. I hope to convince you to be a hater too.

### Background

Project configuration occurs for many reasons. Sometimes there are multiple approaches to a given problem space, for example an asynchronous server runtime might offer multiple backends for different underlying operating system syscalls like `epoll()` or `io_uring`.

When the side-effects of the configuration are limited to the translation units of the project itself it is appropriate to offer these configuration choices at build-time to the packager of the project. The packager makes a decision, and consumers abide by that decision.\[1\]

For such cases it’s traditional for each configuration-specific unit to be confined to its own set of source files, and only the selected configuration gets compiled and linked into the package. This use-case has no overlap with `config.hpp`.

The more interesting kind of project configuration is due to **platform** differences in _consumer translation units_.

### The Problem

In C++ programming we must be constantly aware of “the platform,”\[2\] and more importantly the differences between various platforms our code might be consumed on. When writing header files and interface units there is an additional complication, we have not one platform to deal with, but two.

There is the **packager’s** platform, and the **consumer’s** platform.

Unlike source files, headers and interfaces units are built on the consumer’s platform, and the packager has no insight into what capabilities will be available in that context. If a `config.hpp` file is generated by the packager it will reflect the capabilities of their platform; possibly being inappropriate, even inconsumable, for a consumer on a different platform.

### Motivating Example

Consider a possible implementation of [P1619: Functions for Testing Boundary Conditions on Integer Operations](https://wg21.link/P1619). A natural implementation is header-only, implementing the templates as described in the paper.

However, we may wish to accelerate using compiler builtins where available, to get better codegen for the runtime case. Imagine we use the `__builtin_add_overflow_p` family on `gcc`, and fallback to a generic implementation on other platforms.

Following [**[CPP.NO\_FLAG\_FORKING]**](https://github.com/bemanproject/beman/blob/main/docs/BEMAN_STANDARD.md#cppno_flag_forking), we check for the availability of the symbol at build time and generate a `config.hpp`. A packager builds the package with `gcc` and ships it to a package repository.

Later, a consumer installs the package from the repository and tries to build the project with `clang`. Despite a generic implementation being available, their build fails because `config.hpp` was generated targeting `gcc`.

Here we differed by available compiler builtins, but the case holds for operating system, language standard, stdlib implementation, or any other platform difference you can imagine.

### Alternative Solutions

There are two possible solutions:

1. Allow flag forking. This is the overwhelming industry standard solution (see **[[CORE.INDUSTRY\_STANDARD]](https://github.com/bemanproject/beman/blob/main/docs/BEMAN_STANDARD.md#core-principles)**). Allow for detection of capabilities inside the preprocessor and conditional inclusion of code based on those capabilities.

2. Separate capabilities into their own header files and/or interface units, perform platform introspection on the consumer’s platform (via checks in `<package>-config.cmake`), and add only the relevant platform-specific headers to the include path. This limits consumers to those specifically using CMake and `find_package()`, and ends up being isomorphic to flag forking, simply lifting the fork into the `-I` flags.

Or allow both, with individual projects determining which is best for them. (2) is not explicitly forbidden by the Beman standard right now, so is a viable solution for projects wishing to address this inside the current standards.

* * *

1. The packager is the developer or system which builds, and more importantly, constructs the install tree for the project. The consumer is the developer or system which incorporates the packaged install tree into their own, downstream, project. 

2. A slippery concept which we use the encompass the set of differing capabilities and restrictions within a given translation unit. Many elements contribute to “the platform”, operating system, compiler, machine architecture, and language standard, to name some of the most common.

---

<div class="post-metadata">

**Author:** ![Jeff-Garland](https://yyz1.discourse-cdn.com/flex029/user_avatar/discourse.bemanproject.org/jeff-garland/32/23_2.png) [@Jeff-Garland](https://discourse.bemanproject.org/u/Jeff-Garland)\
**Post date:** [July 6, 2025, 11:58pm UTC](https://discourse.bemanproject.org/t/a-haters-case-against-config-hpp/434/2 "2025-07-06T23:58:59Z")

</div>

Put me in the +1 column here – the generated config.hpp file is a massive issue that breaks header only usage without cmake and cases like godbolt. Besides the library above this solution has created issues in inplace\_vector, scope, any\_view and iterator\_interface – there’s probably more but those are discussions I’ve personally been involved with.

@dsankel says that supporting _dropping headers into a project_ isn’t a case we should support, but I continue to disagree strongly. I’d argue it’s far more important than the more complicated cases which the rule is attempting to provide benefit. Part of this is that the majority of Beman libraries are in fact header-only – and as you observe this is exactly when the code needs to stand alone.

> [@vito.gamberini](#):
>
> Separate capabilities into their own header files and/or interface units, perform platform introspection on the consumer’s platform (via checks in `<package>-config.cmake`), and add only the relevant platform-specific headers to the include path.

I think what you’re suggesting is the moral equivalent of boost.config? To me that seems like overkill for the bounds\_test and many other cases.

Like I’ve argued for [any\_view](https://github.com/bemanproject/any_view/pull/31) I don’t think that selecting the built-in as an implementation detail on gcc impacts in any fashion the desire for link time compatibility – which is at the root of the rule. As a header only choice that’s internal to the offered api, if you compile with gcc you get the optimization in the .o (.so, .a) and there’s no impact on linkage. Or am I missing some nuance?

So after we potentially remove cases where I think the flag forking rule currently over reaches or is misapplied, lets discuss the no-exceptions fork for inplace\_vector. As it stands that BEMAN\_INPLACE\_VECTOR\_NO\_EXCEPTIONS flag _also_ does not impact linkage – however it clearly impacts behavior (abort versus exception). This library also uses the config.hpp _when present_ – and I think that’s also not a violation of the flag forking requirement. Specifically, in the [header here](https://github.com/bemanproject/inplace_vector/blob/b81a3c7dd2e539bf739932ba265de821fd81cd77/include/beman/inplace_vector/inplace_vector.hpp#L6-L8) the library consumes the generated config.hpp if it exists which can set the flags. However, if the file does not exist [the library selects a default](https://github.com/bemanproject/inplace_vector/blob/b81a3c7dd2e539bf739932ba265de821fd81cd77/include/beman/inplace_vector/inplace_vector.hpp#L27-L29). This allows inplace vector [to work in godbolt](https://godbolt.org/z/vbdcb6sE5) without heroics - and for user to copy the header into a project and get appropriate defaults.

tldr: I think we need a much more nuanced definition of _what can be forked_ and what cannot – and we need to allow libraries to set defaults in code like above when the config.hpp does not exist.

Thoughts?

---

<div class="post-metadata">

**Author:** ![vito.gamberini](https://yyz1.discourse-cdn.com/flex029/user_avatar/discourse.bemanproject.org/vito.gamberini/32/261_2.png) [@vito.gamberini](https://discourse.bemanproject.org/u/vito.gamberini)\
**Post date:** [July 7, 2025, 4:19pm UTC](https://discourse.bemanproject.org/t/a-haters-case-against-config-hpp/434/3 "2025-07-07T16:19:13Z")

</div>

> [@Jeff-Garland](#):
>
> I think what you’re suggesting is the moral equivalent of boost.config? To me that seems like overkill for the bounds\_test and many other cases.

I’m unfamiliar with the full scope of what `boost.config` achieves (and trying to get a quick overview from the boost docs was unsuccessful), but this is already implemented in `bounds_test`.

Conceptually it looks like this:

```CMake
if(HAS_GNU_OVERFLOW)
  target_include_directories(beman::bounds_test
    INTERFACE
      "${_IMPORT_PREFIX}/include/beman/bounds_test/plat/gnu"
  )
elseif(HAS_MSVC_OVERFLOW)
  target_include_directories(beman::bounds_test
    INTERFACE
      "${_IMPORT_PREFIX}/include/beman/bounds_test/plat/msvc"
  )
else()
  target_include_directories(beman::bounds_test
    INTERFACE
      "${_IMPORT_PREFIX}/include/beman/bounds_test/plat/generic"
  )
endif()

```

The specific implementation points [are here for the build tree](https://github.com/bemanproject/bounds_test/blob/7862a9f1751880d72743992e2ce71676d35ce968/include/beman/bounds_test/plat/CMakeLists.txt) and [over here for when used via `find_package()`](https://github.com/bemanproject/bounds_test/blob/7862a9f1751880d72743992e2ce71676d35ce968/cmake/beman.bounds_test-config.cmake).

Again this is flag forking via `-I` instead of `-D`, so this seems like [a distinction without a difference](https://en.wikipedia.org/wiki/Distinction_without_a_difference) and I don’t understand what we’re trying to achieve by banning the `-D` option. As has been mentioned this has been discussed ad nauseam so I’m certain I’m late to the party and there’s lots of use cases I’m not considering.

---

<div class="post-metadata">

**Author:** ![vito.gamberini](https://yyz1.discourse-cdn.com/flex029/user_avatar/discourse.bemanproject.org/vito.gamberini/32/261_2.png) [@vito.gamberini](https://discourse.bemanproject.org/u/vito.gamberini)\
**Post date:** [July 7, 2025, 5:13pm UTC](https://discourse.bemanproject.org/t/a-haters-case-against-config-hpp/434/4 "2025-07-07T17:13:54Z")

</div>

> [@Jeff-Garland](#):
>
> As a header only choice that’s internal to the offered api, if you compile with gcc you get the optimization in the .o (.so, .a) and there’s no impact on linkage. Or am I missing some nuance?

I’m unfamiliar with what the discussed use case was. On MSVC none of our inline functions or template instantiations get exported by default, so by taking no action with regards to template/function visibility we’re safe. On GNU-likes it’s up to the consumer of the header only library to correctly use `-fvisibility=hidden` to ensure the internal ABI details are hidden from the world.

In GCC, template instantiations are given the visibility of their template. I’ve implicitly raised a couple times that Beman needs a policy or switch to control visibility, see [Exporting Symbols · Issue #161 · bemanproject/exemplar · GitHub](https://github.com/bemanproject/exemplar/issues/161).

If we either give our templates hidden visibility, or give users a switch to control the visibility of header symbols (preferably defaulting to hidden), the question of ABI collision goes away.
