Search code examples
c++language-lawyer

May a vendor add functions in a standard library header file without #including other headers?


Microsoft has std::max() in <utility>, so this compiles (Godbolt):

#include <utility>

int main()
{
    return std::max(2,3);
}

GCC doesn't have std::max() in <utility>, so it doesn't compile (Godbolt). And neither does clang (Godbolt).

CppReference doesn't list it as part of <utility>. It should be in <algorithm> (CppReference).

The MSVC header <utility> does not #include <algorithm> as allowed per [res.on.headers] (see this answer). Instead, it implements 2 functions directly and forward-declares others:

_EXPORT_STD template <class _Ty, class _Pr>
_NODISCARD constexpr const _Ty&(max) (const _Ty& _Left, const _Ty& _Right, _Pr _Pred) noexcept(
    noexcept(_Pred(_Left, _Right))) /* strengthened */ {
    // return larger of _Left and _Right
    return _Pred(_Left, _Right) ? _Right : _Left;
}

#pragma warning(push)
#pragma warning(disable : 28285) // (syntax error in SAL annotation, occurs when _Ty is not an integral type)
_EXPORT_STD template <class _Ty>
_NODISCARD _Post_equal_to_(_Left < _Right ? _Right : _Left) constexpr const _Ty& //
    (max) (const _Ty& _Left, const _Ty& _Right) noexcept(noexcept(_Left < _Right)) /* strengthened */ {
    // return larger of _Left and _Right
    return _Left < _Right ? _Right : _Left;
}
#pragma warning(pop)

_EXPORT_STD template <class _Ty, class _Pr>
_NODISCARD constexpr _Ty(max)(initializer_list<_Ty>, _Pr); // implemented in <algorithm>

_EXPORT_STD template <class _Ty>
_NODISCARD constexpr _Ty(max)(initializer_list<_Ty>); // implemented in <algorithm>

Is it allowed for a library vendor (like Microsoft) to add functions to arbitrary headers?

The problem I see is portability of my code. This way, my code only works with MSVC and not with others. Worse, I ended up in this situation because I used the feature "Optimize include graph" of Visual Studio, which then decided that <utility> requires less lines of code to be included (or whatever their metric is to optimize includes).

In the Standard, I only find (search term const T& max) the std::max() function in chapter "Header <algorithm> synopsis" (chapter 27.4 in C++23 draft N4928).

This question is not a duplicate of Why does omission of "#include " only sometimes cause compilation failures?. I know why it compiles. It compiles because std::max() is defined directly or indirectly in <utility>. None of the answers there gives a reference to the specification.

Same for Why can std::max and std::min still be used even if I didn't #include ? and Can std::string be used without #include ? and Why Is it possible to use std::thread without #include thread in c++? . Those not questions and none of them gives specification references.


Solution

  • using.headers#3 says:

    A translation unit [...] shall include the header [...] lexically before the first reference in that translation unit to any of the entities declared in that header. No diagnostic is required.

    The code references std::max, but includes only <utility>. Yet, utility.syn does not list max anywhere. Therefore, the program has undefined behavior is ill-formed, no diagnostic required.

    That the program still compiles, does not make it correct. Nevertheless, the compiler is conforming because it is not required to diagnose that <algorithm>, where std::max is actually declared, was not included.