Explanation

Limits

What cppgo cannot do, and what to do instead.

There are three kinds of limit, and they behave differently:

  • hard limits — properties of the platform or the ABI, the application binary interface: how a compiled call is laid out, which registers and which memory carry its arguments and its result. No amount of generator work removes them.
  • not supported yet — the construct does not bind, and the generator says so with a message naming the declaration and the fix. A declaration is left out with a warning; a template pattern that stops the run is an error. The construct never silently degrades.
  • designed — stated in the design, not in this build. Marked as such wherever it appears in the documentation.

Hard limits

Four things in this table need saying first. A binding is one C++ declaration turned into a callable Go function. An exception unwinds: it walks the frames above the throw, looking for a catch (a handler), and needs unwind information for every frame it crosses. A foreign call is a call from Go into machine code the Go toolchain never compiled. The register file is the fixed set of CPU registers a calling convention passes arguments in — nothing on the stack counts, and the callback path reads only those registers.

LimitWhyWhat you can do
An exception with no C++ frame terminates the processthe direct path has no handler above it, and unwinding through Go frames is not supported, because a Go frame carries no unwind informationgive the library a noexcept entry point — one that promises not to throw — that reports failure as a status
A callback cannot be called from a direct bindingthe runtime only accepts a callback on a thread already inside a foreign callnone: bindings that take a Go function, and every binding of a package that registers a retained callback, use the reentrant carrier automatically
A callback the callee stores is not inferreda function pointer says nothing about how long the callee keeps itname the declaration with -retain: the registering call then returns a registration, and Close releases it
A C++ thread Go did not start cannot deliver a callbackthe runtime has no goroutine to run the Go function ondeliver from a call made from Go; a library that logs from its own thread cannot be bound this way
A callback carries at most 6 integer and 8 floating-point arguments, and one resultthe callback entry receives the register file and nothing elsetake a pointer to a struct
A std:: layout appears only in C++constructing a std::string or std::function (C++’s wrapper around any callable) from a Go-side layout — the type’s in-memory representation, the bytes and their order — works only for instantiations the library happens to exportlet cppgo construct it (it does, for a parameter or a result)
A function-like macro that is the API cannot be bounda macro is a text substitution performed before compilation, and its expansion is not a declarationask the library for a function
A template type nobody asked for does not exista template only exists where it is instantiatedrun cppgo build -scan with the program that uses it
long double and __int128 have no Go value typethey are the two C++ types wider than 64 bits, and there is no Go type of that widthwiden or narrow at the C++ boundary
The C++ side must share your library’s ABI and standard librarythe two call each other across that boundarybuild with the same compiler family and standard library; nothing detects a mismatch for you

Not supported yet

Two things can happen, and they cost different amounts. A declaration the build cannot carry is a warning: the rest of the header binds, the package is written, and one line on stderr names the declaration and the fix. A pattern the generic API cannot model stops the whole header with an error: nothing is written, because the pattern has no shape the two-phase flow can emit.

A warning that leaves one declaration out has this shape, and a row whose message differs quotes the whole line:

cppgo: warning: <declaration> is not bound: <what is wrong>; <fix>

An error names the file and the line of the declaration:

cppgo: <file>:<line>: <declaration>: <what is wrong>; <fix>

Most rows quote the <what is wrong>; <fix> half, which is what you read after is not bound:. A handful of terms need defining first. A record is a struct or class used as a bundle of fields, and it is mirrorable when Go can hold its bytes directly — every field public, the natural Go layout, and no pointer, reference or other field Go cannot represent. A POD — plain old data — is the strongest form of that: a record with no constructor, no virtual function and no private members, whose bytes are its whole representation. An opaque record is the other extreme: a C++ struct whose layout Go cannot see, so Go holds only a pointer to storage C++ owns. A template pattern is a class or function template before any concrete type is substituted, such as template <typename T> class Box; a dependent type is a type that mentions a template parameter, such as std::vector<T>, and a parameter pack is the ... that stands for an argument list of unknown length. The messages also name the convention’s two classes of argument register: INTEGER, the six general-purpose ones, and SSE, the eight floating-point ones beside them.

A declaration the build does not bind (warning)

ConstructMessageWorkaround
a variadic function (a parameter list ending in ...)a variadic function has no fixed argument list to lay out; replace the ellipsis with a fixed parameter list or a spana fixed list, or a pointer and a length
a member function template<declaration> is a member function template and is not bound: name its concrete instantiation as a non-template member beside ita non-template member
an operator outside the naming table"<operator>" is an operator outside the naming table; rename it with -rename '<signature>=<GoName>'-rename, or a named function
an operator of a class template<signature> is not part of the generic API: an operator of a class template is bound on each concrete specialization insteadbind the specialization
a member of a class template whose signature names a type declared inside it<signature> is not part of the generic API: its signature names a type declared inside <template>, and each instantiation has its own; the specialization carries itbind the specialization
a std::unique_ptr or std::shared_ptr parameter"std::unique_ptr<T>" is a smart-pointer parameter; take the object by reference or by pointer at the C++ boundarya reference or a pointer
a std::span result"std::span<int>" is a span result; take the span as a parameter, or return the container it viewsa container result, or the span as a parameter
a std::vector<std::string>"std::string" is a standard-library element this build does not cross inside a container; use a scalar, a POD record, or a std::vector of those as the elementa std::vector of scalars or records, or a wrapper
a function pointer or std::function result"std::function<...>" is a function or std::function result; take it as a callback parameter, or expose a C++ wrapper that owns itan out-parameter, or a wrapper
a C array"T [N]" is a C array; decay it to a pointer and a length at the C++ boundarya pointer and a length
a non-const char * parameter"char *" is a non-const char pointer; take it as const char *: the Go string is immutable, so the callee's writes would not reach itconst char *
a pointer to an opaque record returned by a free function"T *" is a pointer to an opaque record; return it through a reference at the C++ boundarya reference; a method’s borrowed pointer is a view and binds
void *"void *" is a void pointer; expose a typed pointer at the C++ boundarya typed pointer
std::nullptr_t"std::nullptr_t" is nullptr_t; pass a typed null pointer at the C++ boundarya typed pointer
a non-const reference to a std:: value"std::string &" is a non-const standard-library reference; take it by value or by const reference; copy-back is not emitted by this build yeta value or a const reference
a standard-library type outside the emitted table (std::deque, std::map, …)"std::deque<int>" is a standard-library type outside the emitted table; cross it as a scalar, T*/length or POD, or bind a C++ wrapper with a supported signaturea wrapper with a supported signature
a pointer to a std:: value"std::string *" is a pointer to a standard-library type; take it by value or by reference at the C++ boundarya value or a reference
long doublecppgo: long double: long double and __int128 have no Go value type; widen or narrow it at the C++ boundary to double or int64, or wrap it in a representable typewiden or wrap
__int128, or an integer width the type map does not carry"__int128" has a 16-byte width with no Go value type; widen or narrow it to a fixed-width integer at the C++ boundarywiden or narrow
an incomplete type, or a record the selection did not keep"T" is not part of the generated API; include its complete definition in the header — a specialization says name it with -instantiate '<Specialization>'include it, or -instantiate
a record passed by value whose layout is unknown"T" is passed by value but its layout is unknown; make the type complete in the header, or take it by reference at the C++ boundarya complete type or a reference
a callback taking more than 6 INTEGER arguments"int (*)(...)" takes more than 6 INTEGER arguments; take a pointer to a struct insteada struct pointer
a callback taking more than 8 SSE arguments"double (*)(...)" takes more than 8 SSE arguments; take a pointer to a struct insteada struct pointer
a callback argument with no Go shapethe callback argument "T" is not a scalar; take an integer, a double or a pointer instead — a float says the callback argument "float" is a float; take a double instead: a float argument is not carried by this build yeta scalar, a (pointer, length) pair or a const char *
a standard-library view on a callback-capable callthe parameter "<name>" of a callback-capable call is a std::span<T>, a view of Go memory the callee keeps for the whole call — a std::string_view says the samea copied container (std::vector, std::string) or a (pointer, length) pair
a standard-library container the callee replaces, on a callback-capable callthe parameter "<name>" of a callback-capable call is a std::vector<T> the callee replaces; take it by value or by const reference: the copy-back the thunk writes is a word of the goroutine's framea value or a const reference
a void * on a callback-capable callthe parameter "<name>" of a callback-capable call is a void *; expose a typed pointer at the C++ boundary: a void pointer names memory nothing can copya typed pointer
a callback-capable call that returns a standard-library value other than std::string"<signature>" returns a std::vector<T>, and a call that may run a callback copies a standard-library result out through the goroutine's frame; return the value through an out parameter the callee fills, or as a (pointer, length) pairan out-parameter, a (pointer, length) pair, or no callback
a retained declaration that returns a value-retain <declaration>: the declaration returns a value, and a retained registration consumes the call's result: drop the result, or mark it with -status and return a status codea void declaration, or a status code with -status
a -retain entry that names nothing, or a declaration with no callback-retain <declaration>: it names nothing this header declares, or the declaration takes no callback parameter, so there is nothing the callee could storethe declaration whose callback parameter the callee stores
a call whose out-parameters fill the register file after the argument blockthe call needs N words after the argument block, past the register file — a word the block cannot carry says the argument block cannot carry the word "<type>"fewer out-parameters
a callback call past the register filethe callback call needs N INTEGER and M SSE words, past the register filea smaller signature
a public field whose type the selection dropped<Record>::<field> is not bound: its type, <Type>, is not part of the generated APIbind the type too
a public field that is a raw pointer to a record<Record>::<field> is not bound: "T *" is a pointer to a record, and nothing on the Go side owns it; expose the pointed-to object through a method, or take the address only inside C++a method
a public field whose value cannot be assigned<Record>::<field> has no setter: a std::string_view does not own its storage — a record field says its type's copy assignment is not publicthe getter only
a record whose sizeof or alignof is unknown<Record> has no layout the Go side can allocate (sizeof or alignof is unknown), so no factory is generated for it; reach it as a view of a field or of a borrowed resulta view
an enum of a template specialization whose values cannot be read<Enum> is not bound: the enumerator values of a template specialization's enum cannot be read from the pattern; give them literal initialisersliteral initialisers
a specialization synthesized from a pattern that has base classes<Record> inherits N base class(es) and its inherited members are not copied: a specialization synthesized from a pattern cannot prove that a base member is valid for this instantiation; bind the members through As<Base>()As<Base>()

A run the build refuses (error)

These stop the run instead of leaving one declaration out: a template pattern the generic API cannot model, two declarations that need the same Go name, or a -status entry the declaration cannot satisfy. Nothing is written.

ConstructMessageWorkaround
a public field on a class templatecppgo: <file>:<line>: <record>: the public field <name> of a template is not emitted by this build yet; make the field private and expose an accessor in C++, or bind the instantiation with -instantiatea private field with an accessor, or -instantiate
a function template whose signature names a dependent type — a parameter pack such as af::eval, or a type such as Eigen::NoAliascppgo: <file>:<line>: <function>: result: "<type>" is not a type this build classifies; expose it as a scalar, a pointer, a reference or a supported std:: type — a parameter says parameter <name>: instead of result:write the signature with a scalar, a pointer or a reference
a template parameter that is not a type or an integerthe <kind> template parameter <name> is not a type or an integer; expose a template-template parameter as a concrete type at the C++ boundarya concrete type
a non-type template parameter that is not an integerthe non-type template parameter <name> is not an integer; expose the value as an integral template parameter at the C++ boundaryan integer parameter
a class template with no parametersa class template with no parameters is not a generic type; name a concrete instantiation with -instantiate-instantiate
two declarations that map to one Go name, when no overload suffix separates themcppgo: <signature> and <signature> both map to Go name <Name>; rename one with -rename '<signature>=<GoName>' — the type forms say two records map to Go type <Name> or two types map to Go type <Name>, and two templates say both map to the Go name <Name>-rename
-status on a return that is not a 32-bit integercppgo: -status <signature>: the return type is <type>, and a status is a 32-bit integer — a void return says the declaration returns nothing, so there is no status to readdrop -status, or bind the declaration without it

char * parameters

An unpaired const char * crosses as a Go string in both directions: a parameter is copied into a NUL-terminated buffer for the call, and a result is copied out of the library’s storage.

A (const char *, size_t) pair does not become a string: the pointer and the length are recognised as a pair first, so the parameter is a Go []int8 — a borrowed byte slice the callee reads during the call. That is the pair form of the type map, and it is the one shape where the generated Go and the string the map states for a C string differ.

A non-const char * parameter is the one form that does not bind: the callee would write into the Go string, which is immutable, so the warning in the table above asks for const char *. Use std::string or std::string_view — a non-owning pointer-and-length view of a string — when the C++ side needs the pointer and a length as one argument.

Designed, not shipped

The first row is the error projection: turning each of the ways a C++ library reports failure into a Go error.

ItemWhat is missingWhat you can rely on today
the full error projectionstd::error_code (a library’s own error value), std::expected (a value, or the error instead), errno (C’s per-thread error number), std::variant (one of several types), a configured bool + out-parameter, custom result typesexceptions and -status become Go errors; a declaration returning std::error_code is left out with a warning
generics breadthclass templates bind for scalar type arguments and non-type dimensions (a template parameter that is a value rather than a type); a public field on a class template, a function template whose signature names a dependent type, and a parameter pack are not part of the generic APIevery refusal names the declaration and the fix
the image, array and matrix work of the three target librariescv::Mat, af::array and Eigen’s matrix types: a class binds only up to what can cross, so a constructor the generator cannot carry is left out with a warning, and Eigen’s usual Options=0 has no phantom type in the generic APIeach library ships a slice that binds and tests the declarations the generator carries today: OpenCV, ArrayFire, Eigen
embedded generated packagescppgo.OpenFS works; Open, the load function cppgo gen writes, does not call it yetdeploy the two C++ files, or use the runtime directly

What is not a limit

Three things look like limits and are not:

  • A record Go cannot mirror. A bitfield (several fields packed into the bits of one integer), a packed struct (its padding removed), a union (several members sharing one storage), a private or mixed-access field, or a field Go cannot represent makes the record opaque, not refused: Go holds a pointer to C++ storage, and every public field keeps a getter and, when the C++ assignment is public, a setter. What you lose is the Go struct whose layout is the C++ layout, not the type.
  • A missing declaration in the report. The report is the list of bindings cppgo gen prints. If a declaration is absent from it, the declaration was not selected (-bind / -exclude) or the parse — the generator’s reading of your headers — did not see it. A parse without the project’s real flags says so with a warning; a wrong include path binds a different program.
  • A call that cannot use the direct path. It is not a degradation of the API: the shim — the C++ file cppgo generates — carries the call, and the Go-side cost of it is the same (Performance).