how to support user-defined C++ types inside <complex>?

Universal is a cross-platform library for plug-in replacement types for C++. With the move to Xcode 15, we have encountered a compilation failure in C++ <complex> support.

I am looking for insight into the design of std::isinf/std::isnan to resolve the following compilation failure:

[ 35%] Building CXX object static/fixpnt/binary/CMakeFiles/fixpnt_mod_complex_mul.dir/complex/mod_complex_mul.cpp.o In file included from /Users/runner/work/universal/universal/static/fixpnt/binary/complex/mod_complex_mul.cpp:7: In file included from /Users/runner/work/universal/universal/./include/universal/utility/directives.hpp:32: In file included from /Applications/Xcode_15.0.1.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX14.0.sdk/usr/include/c++/v1/iostream:43: In file included from /Applications/Xcode_15.0.1.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX14.0.sdk/usr/include/c++/v1/ios:221: In file included from /Applications/Xcode_15.0.1.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX14.0.sdk/usr/include/c++/v1/__locale:18: In file included from /Applications/Xcode_15.0.1.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX14.0.sdk/usr/include/c++/v1/mutex:191: In file included from /Applications/Xcode_15.0.1.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX14.0.sdk/usr/include/c++/v1/__memory/shared_ptr.h:42: In file included from /Applications/Xcode_15.0.1.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX14.0.sdk/usr/include/c++/v1/atomic:2668: /Applications/Xcode_15.0.1.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX14.0.sdk/usr/include/c++/v1/cmath:607:12: error: no matching function for call to 'isinf' return std::isinf(__lcpp_x); ^~~~~~~~~~ /Applications/Xcode_15.0.1.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX14.0.sdk/usr/include/c++/v1/complex:598:29: note: in instantiation of function template specialization 'std::__constexpr_isinf<sw::universal::fixpnt<4, 0>>' requested here bool __z_inf = std::__constexpr_isinf(__a) || std::__constexpr_isinf(__b); ^ /Users/runner/work/universal/universal/static/fixpnt/binary/complex/mod_complex_mul.cpp:74:18: note: in instantiation of function template specialization 'std::operator*<sw::universal::fixpnt<4, 0>>' requested here result = a * b; ^ /Users/runner/work/universal/universal/static/fixpnt/binary/complex/mod_complex_mul.cpp:187:42: note: in instantiation of function template specialization 'VerifyComplexMultiplication<4UL, 0UL, true, unsigned char>' requested here nrOfFailedTestCases += ReportTestResult(VerifyComplexMultiplication< 4, 0, Modulo, uint8_t>(reportTestCases), "fixpnt< 4, 0,Modulo,uint8_t>", test_tag); ^ /Applications/Xcode_15.0.1.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX14.0.sdk/usr/include/c++/v1/math.h:429:87: note: candidate function not viable: no known conversion from 'sw::universal::fixpnt<4, 0>' to 'float' for 1st argument _LIBCPP_NODISCARD_EXT inline _LIBCPP_CONSTEXPR_SINCE_CXX23 _LIBCPP_HIDE_FROM_ABI bool isinf(float __x) _NOEXCEPT { ^ /Applications/Xcode_15.0.1.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX14.0.sdk/usr/include/c++/v1/math.h:433:114: note: candidate function not viable: no known conversion from 'sw::universal::fixpnt<4, 0>' to 'double' for 1st argument _LIBCPP_NODISCARD_EXT inline _LIBCPP_CONSTEXPR_SINCE_CXX23 _LIBCPP_HIDE_FROM_ABI _LIBCPP_PREFERRED_OVERLOAD bool isinf(double __x) _NOEXCEPT { ^ /Applications/Xcode_15.0.1.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX14.0.sdk/usr/include/c++/v1/math.h:437:87: note: candidate function not viable: no known conversion from 'sw::universal::fixpnt<4, 0>' to 'long double' for 1st argument _LIBCPP_NODISCARD_EXT inline _LIBCPP_CONSTEXPR_SINCE_CXX23 _LIBCPP_HIDE_FROM_ABI bool isinf(long double __x) _NOEXCEPT { ^ /Applications/Xcode_15.0.1.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX14.0.sdk/usr/include/c++/v1/math.h:417:80: note: candidate template ignored: requirement 'std::is_arithmetic<sw::universal::fixpnt<4, 0, true, unsigned char>>::value' was not satisfied [with _A1 = sw::universal::fixpnt<4, 0>] _LIBCPP_NODISCARD_EXT _LIBCPP_CONSTEXPR_SINCE_CXX23 _LIBCPP_HIDE_FROM_ABI bool isinf(_A1 __x) _NOEXCEPT { ^ /Applications/Xcode_15.0.1.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX14.0.sdk/usr/include/c++/v1/math.h:424:5: note: candidate template ignored: requirement 'std::is_arithmetic<sw::universal::fixpnt<4, 0, true, unsigned char>>::value' was not satisfied [with _A1 = sw::universal::fixpnt<4, 0>] isinf(_A1) _NOEXCEPT { ^

The failure to cast the fixpnt type to native IEEE floats would be difficult to solve as the design disables automatic conversions to avoid difficult to find numerical bug behavior.

However, the std::arithmetic<> error makes sense, but I would like to understand the design intent behind this, as this is the only C++ stack that throws this constraint.

The Universal library can be found at https://github.com/stillwater-sc/universal

Does the library define isinf() for this fixpnt type?

Yes, it does through this mechanism:

#pragma once // math_classify.hpp: classification functions for fixpnts // // Copyright (C) 2017 Stillwater Supercomputing, Inc. // SPDX-License-Identifier: MIT // // This file is part of the universal numbers project, which is released under an MIT Open Source license.

namespace sw { namespace universal {

// STD LIB function for IEEE floats: Categorizes floating point value arg into the following categories: zero, subnormal, normal, infinite, NAN, or implementation-defined category. template<unsigned nbits, unsigned rbits, bool arithmetic, typename BlockType> int fpclassify(const fixpnt<nbits, rbits, arithmetic, BlockType>& a) { return std::fpclassify((long double)(a)); }

// STD LIB function for IEEE floats: Determines if the given floating point number arg has finite value i.e. it is normal, subnormal or zero, but not infinite or NaN. // specialized for fixpnts template<unsigned nbits, unsigned rbits, bool arithmetic, typename BlockType> inline bool isfinite(const fixpnt<nbits, rbits, arithmetic, BlockType>& a) { return true; }

// STD LIB function for IEEE floats: Determines if the given floating point number arg is a positive or negative infinity. // specialized for fixpnts template<unsigned nbits, unsigned rbits, bool arithmetic, typename BlockType> inline bool isinf(const fixpnt<nbits, rbits, arithmetic, BlockType>& a) { return false; }

// STD LIB function for IEEE floats: Determines if the given floating point number arg is a not-a-number (NaN) value. // specialized for fixpnts template<unsigned nbits, unsigned rbits, bool arithmetic, typename BlockType> inline bool isnan(const fixpnt<nbits, rbits, arithmetic, BlockType>& a) { return false; }

// STD LIB function for IEEE floats: Determines if the given floating point number arg is normal, i.e. is neither zero, subnormal, infinite, nor NaN. // specialized for fixpnts template<unsigned nbits, unsigned rbits, bool arithmetic, typename BlockType> inline bool isnormal(const fixpnt<nbits, rbits, arithmetic, BlockType>& a) { return true; }

}} // namespace sw::universal

Please edit your posts to format the code properly!

Try making those functions constexpr.

I say this because in your original error message you have something like this:

error: no matching function for call to 'isinf'
in instantiation of function template specialization 'std::__constexpr_isinf<sw::universal::fixpnt<4, 0>>'

It then lists all the overloads of isinf that it can't use, and yours isn't there. So either it can't see it for some reason, or it has seen it but it has excluded it. The name __constexpr_isinf gives a strong hint that it is a constexpr function, so it will only call other functions (i.e. isinf) that are also constexpr.

I'm not totally confident of this, because I would have hoped for it to indicate that your isinf has been excluded due to not being constexpr. But you should make all those functions constexr anyway, because they obviously are constexpr and there is no disadvantage to doing so.

Also, the fact that you're seeing this with an Xcode upgrade that has changed the default C++ variant to C++23, and that std::isinf etc. have become constexpr since C++23, is a clue.

Good suggestion but that has not resolved the issue: still the same error.

I would like to understand this error message:

/Applications/Xcode_15.0.1.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX14.0.sdk/usr/include/c++/v1/math.h:417:80: note: candidate template ignored: requirement 'std::is_arithmetic<sw::universal::fixpnt<4, 0, true, unsigned char>>::value' was not satisfied [with _A1 = sw::universal::fixpnt<4, 0>]
_LIBCPP_NODISCARD_EXT _LIBCPP_CONSTEXPR_SINCE_CXX23 _LIBCPP_HIDE_FROM_ABI bool isinf(_A1 __x) _NOEXCEPT {
                                                                               ^
/Applications/Xcode_15.0.1.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX14.0.sdk/usr/include/c++/v1/math.h:424:5: note: candidate template ignored: requirement 'std::is_arithmetic<sw::universal::fixpnt<4, 0, true, unsigned char>>::value' was not satisfied [with _A1 = sw::universal::fixpnt<4, 0>]
    isinf(_A1) _NOEXCEPT {
    ^

It would make sense that as an plugin-type author you would want to support this overload, but it is the first time I am seeing this, so need to do some research on why this dependency is injected.

I would like to understand this error message:

It is listing some of the candidate isinf functions that it has ignored while looking for one that matches your fixpnt type. It is correctly ignoring those functions, because they are enabled only for std::is_arithmetic types, which yours is not (correctly).

It would make sense that as an plugin-type author you would want to support this overload

I'm not sure exactly what you mean by that. You don't want those specific functions in math.h to match your type; that makes no sense. What you do want is your own isinf implementation to be found, and not ignored, by the caller.

Have you checked if it works in C++20 mode?

What namespace is your isinf defined in? This is something I recall getting wrong once. Should it be:

namespace foo {
  class Number;
  constexpr bool isinf(Number);
};

or

namespace foo {
  class Number;
};
constexpr bool isinf(foo::Number);

?

Thank you for the confirmation that a plugin type should NOT provide a std::is_arithmetic<> implementation. I take it that std::is_arithmetic is designed to identify native language types, not the concept of an arithmetic type.

Everything lives in namespace sw::universal. We have:


namespace sw {
   namespace universal {

      // STD LIB function for IEEE floats: Determines if the given floating point number arg is a positive or negative infinity.
      // specialized for fixpnts
      template<unsigned nbits, unsigned rbits, bool arithmetic, typename BlockType>
      inline constexpr bool isinf(const fixpnt<nbits, rbits, arithmetic, BlockType>& a) {
	       return false;
     }

   }
}

The lib and its regression suite passes on msvc, ICC, GCC, and Clang, on Windows, Linux, and worked on MacOS prior to Xcode 15, but since GitHub Actions has progressed 'macos-latest' to Xcode 15, the CI is failing for MacOS/Xcode 15. It passes in

OS Version: macOS 12.7.5 (21H1222)
Kernel Version: Darwin 21.6.0
Image Version: 20240630.1

and

OS Version: macOS 13.6.7 (22G807)
Kernel Version: Darwin 22.6.0
Image Version: 20240630.1

started failing in

OS Version: macOS 14.5 (23F79)
Kernel Version: Darwin 23.5.0
Image Version: 20240701.1

I have had a look at <complex> and <cmath>.

It seems that <complex> has a lot of code that applies only during constant evaluation. So in your case, operator* calls std::__constexpr_isinf (and isnan, isfinite etc).

Looking at the implementations of those, starting around line 570 of <math>, there are variations depending on whether the type is_floating_point and whether a builtin is available. Eventually, it calls either __builtin_isinf(), isinf(), or std::isinf(). Notably this is inside namespace std { .... }; so is there a difference between calling isinf() and calling std::isinf()?

For extra excitement, earlier in the file there is using ::isinf, i.e. isinf() from the global namespace is being imported into std::.

Answering my question in the previous post, your definition of your isinf is in your own namespace, not global. So... when __constexpr_isinf calls std::isinf, which it does for non-floating-point types, it won't find yours - though it would have found it if you had defined it in the global namespace.

I've spent a while on this now and I have other things to do. I think if I were you, I'd now try to create a minimal example of the problem.

Just to provide closure on this, a commit was added to the Universal repo on July 14 that conditionally excludes the failing test code above from builds, because (according to the code comments) "XCode14 and Xcode15 have std::complex libs that do not support user defined types".

how to support user-defined C++ types inside &lt;complex&gt;?
 
 
Q