Skip to content

Conversation

TysonRayJones
Copy link
Member

@TysonRayJones TysonRayJones commented May 6, 2025

Example PR A

Sub PR 4

This PR demonstrates how to add new QuEST API functions which directly return a qcomp.

extern "C" {

    // here be dragons
    qcomp calcAmpSum(Qureg qureg) {
        qcomp value = internalFunc(qureg);
        return value;
    }

}

The problem

QuEST is agnostic to C and C++ using a myriad of tricks. This includes resolving the qcomp type to the natural, respective C and C++ complex types. While a C user may believe the qcomp is defined as

#include <complex.h>
typedef double _Complex qcomp;

a C++ user (and the internal QuEST library) may meanwhile believe it to be

#include <complex>
typedef std::complex<double> qcomp;

As such, a C user might pass a _Complex to a C++ function expecting a std::complex<double>. Alas, this is undefined behaviour! For historical reasons, complex primitives were never integrated into the ABI. It is ergo illegal for a C user to directly pass a qcomp to its internal C++ functions.

The solution

While the type qcomp differs between C and C++, they do have the same memory layout. This is guaranteed by the C99 and C++11 standards as indicated here, and means it is okay to pass pointers to qcomp over the ABI. Ergo, QuEST's C++ functions can safely accept and return qcomp* and be directly invoked by C code.

Passing superfluous pointers is gross though, and we still wish for the C and C++ interfaces to be identical. So, to define a new API function which returns a qcomp, we...

  1. Declare the function in the user-facing header (e.g. quest/include/calculations.h) without extern "C".
    qcomp calcAmpSum(Qureg qureg);

    Both the user's C compiler and the backend's C++ compiler will interpret qcomp as their native types.

  2. Define the function's C++-only version in the corresponding source file (e.g. quest/src/api/calculatons.cpp), also without extern "C".
    qcomp calcAmpSum(Qureg qureg) {
        qcomp value = internalFunc(qureg);
        return value;
    }

    The C++ compiler will name-mangle this definition, ensuring it is never directly invoked from C. So far, C++ users can call this function - we now need to spoof it for C users.

  3. Define a de-mangled C-compatible version of the function just under the above function, which calls the above function, but safely "returns" the qcomp by overwriting a pointer.
    extern "C" void _wrap_calcAmpSum(Qureg qureg, qcomp* out) {
        *out = calcAmpSum(qureg);
    }

    This could be directly called by a C user, but it's ugly and has a differing signature to the C++ version! To address this...

  4. Define a C-only version of the function in quest/include/wrappers.h with an identical signature to the C++ definition but which invokes _wrap_calcAmpSum above.
    #ifndef __cplusplus
    
    extern void _wrap_calcAmpSum(Qureg qureg, qcomp* out);
    
    qcomp calcAmpSum(Qureg qureg) {
    
        qcomp out;
        _wrap_calcAmpSum(qureg, &out);
        return out;
    }
    
    #endif

    Since only C parses this header-only definition, only C user's will invoke it! C++ users will invoke the original calcAmpSum definition.

A regrettable side-effect of this trick is that the intendedly private function _wrap_calcAmpSum is declared (as extern) in the header. This is necessary to prevent the the C compiler parsing wrappers.h from panicking about the missing symbol, but means C users can see and call _wrap_calcAmpSum. Although calling it is harmless, hopefully the ugly prefix dissuades IDEs from listing the routine!

This PR

We generalise the existing calcRealAmpSum (which returned a real-valued primitive without problem) to return qcomp, retaining a C and C++ agnostic API via the above process. We also define its unit test for completeness.

The combined changes of this and the previous example PRs can be seen in #615.

and unit test, for completeness
@TysonRayJones TysonRayJones added the demo A demonstration of a development process label May 6, 2025
@TysonRayJones TysonRayJones merged commit b2524fa into example-pr-3 May 6, 2025
@TysonRayJones TysonRayJones deleted the example-pr-4 branch May 6, 2025 16:15
This was referenced May 7, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
demo A demonstration of a development process
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant