importcpp
pragma to bind to C++ Standard Template
Libraries like std::list
.I like ❤️ using Nim as my go-to programming language – be it for scripting, some CLI tool or any other application. The nice thing when using Nim is that you don’t have to throw away any of the past work done in C or C++, because it is pretty easy to bind those C/C++ libraries and extend their functionality in Nim.
To demonstrate that extensibility, in this post, I will add some new
API in Nim that builds on top of the std::list
STL
The C++ STL is a set of template classes to provide common programming
data structures and functions such as lists, stacks, arrays, etc.
library. I won’t be re-writing any of the std::list
methods in Nim
— No need to re-invent the std::list
wheel in Nim! — I will
directly use the C++ std::list
methods to build new API functions in
Nim. 🤯
std::list
elements #From the std::list reference, we can see that we cannot access a
list element by index using syntax like list_obj[index]
. At the
end of this post, you will be able to convert C++ list objects to
Nim sequences and make the list data super-accessible — You can
print them using echo
, use built-in libraries like sequtils
, and
more.
importcpp
pragma #The first thing we’ll need is to be able to call the C++ list
methods directly in Nim, and we will need the importcpp
pragma
This post will highlight some key features of this pragma. For full
details, check out this section in the Nim Manual linked in
References.
for that. This pragma is used to import a C++ method or type into
the Nim space.
This pragma is typically used like this:
{.link: "/path/to/libCppCode.so".}
type
MyNimType {.importcpp: "MyCppType", header: "libCppCode.hpp".} = object
proc myNimProc(): MyNimType {.importcpp: "myCppMethod", header: "libCppCode.hpp".}
var
foo = MyNimProc()
MyCppType
type from the C++ library is mapped to MyNimType
You would typically name the type and method identifiers same same
when binding external libraries to Nim, just to prevent any
confusion. You can name them differently if you want to though.
in the Nim code.myCppMethod
from C++ is mapped to myNimProc
.myNimProc
in the Nim code along with
other Nim stuff.link
pragma is needed if we need to tell the Nim compiler
where to find the implementation
For the case of linking to the C++ STL <list>
header, we do not
need to provide the std::list
implementation because the GNU GCC
compiler provides that.
of linked methods.<list>
header #Below code snippet shows us all the <list>
header bindings we will
need to meet the goal of this post.
|
|
std::list
type with the Nim
List
type. So, a List[cint]
type in Nim
get mapped to std::list<int>
in C++.iterator
type from
std::list
with the Nim type ListIter
.
<'0>
See 📖 importcpp
for procs for details on such special
patterns.
in the importcpp
argument for this binding. The apostrophe
followed by an integer i is replaced by the i’th parameter in
the ListIter[T]
type i.e. the T
type. So, ListIter[cint]
in Nim gets mapped to std::list<int>::iterator
in C++.()
with
the Nim proc initList[T]()
. As the constructor returns a list
type, the return type on the Nim side set to the equivalent
List[T]
.
importcpp
argument
for this binding: <'*0>
. If we had not used that asterisk
there, that would have mapped to the proc’s return type,
List[T]
. But we want the C++ mapped constructor to look like
std::list<int>()
i.e. we need T
and not
List[T]
in the C++ template type. The *
does the job of
deriving T
from its “container” type List[T]
.size
and
begin
on C++ get mapped to same named procs on the Nim side, with
the correct return types.
csize_t
is a Nim type that maps to the C size_t
type.begin
returns an iterator handle, the return type of
begin
is ListIter[T]
.std::list
prefix for these mappings,
because these procs will always be called in context of their
arguments. For example, listIterVar.size()
in Nim will map to listIterVar.size()
in
C++..*
with Nim dereferencing operator []
. The input argument type is
an iterator. So if iter
is a variable of type ListIter[cint]
,
iter[]
will return the value of type cint
pointed to by that iterator.
importcpp
argument for this mapping has a different pattern:
*#
. The #
is replaced by the first argument of the proc:
iter
. So iter[]
in Nim code
iter[]
is same as writing `[]`(iter)
, but one might agree that the
former style is better and more readable.
will translate to *iter
1 on the C++
side.We have the <list>
bindings ready, but now we need some test C++
code which will be our “library” for use in the Nim code.
So here it is:
void generateList(int num, std::list<int> *retList)
{
for (int i = 0; i < num; ++i) {
retList->push_back(i * 2);
}
}
The C++ “library” here has a generateList
API with expects an
integer num
as the first arg, and it save a num
element
std::list<int>
object to the pointer passed as its second arg.
Now, we can either go the conventional route of compiling this code to
a shared object (.so
or .dll
) and then linking it to the Nim code
using the link
pragma.
But.. that is boring 💤
We will do something cooler.. we will inline that C++ code in Nim
using the emit
pragma and bind to it using importcpp
just as we
did for the <list>
methods! 😎
{.emit: """
void generateList(int num, std::list<int> *retList)
{
for (int i = 0; i < num; ++i) {
retList->push_back(i * 2);
}
}
""".}
proc generateList(num: cint; lPtr: ptr List[cint]) {.importcpp: "generateList(@)".}
Here, I also introduce you readers to a new importcpp
pattern:
@
. The @
expands to comma-separated arguments passed on the Nim
side.
Note the equivalence between the C++ function signature
void generateList(int num, std::list<int> *retList)
and Nim proc signature proc generateList(num: cint; lPtr: ptr List[cint])
.
Below Nim code will call the test C++ generateList
method defined
above and then print the whole list object after converting it to a
Nim sequence.
var
l = initList[cint]()
generateList(10, addr l)
var
s = l.toSeq()
echo "sequence = ", s
initList
bindings defined in
Code Snippet 2 is used to initialize the list object
l
.generateList
which will
populate the Nim allocated memory with 10 elements.toSeq
proc (that we will define in the next
section) to convert that list to a Nim sequence.std::list
API in Nim #The end goal is to be able to write a toSeq
proc that can convert a
std::list
object to a Nim sequence. Here’s a psuedo-code I began
before it evolved into Code Snippet 7.
proc toSeq (l: list_obj): sequence
lBegin = handle to the iterator for the beginning of list_obj; use `begin`
lSize = number of elements in list_obj; use `size`
lIter = lBegin
for idx in 0 ..< lSize:
sequence[idx] = *lIter; use the `[]` dereferencing operator
lIter = pointer to the next element; use `next` from <iterator>
As it turned out, the real Nim code wasn’t longer or more complicated than the pseudocode 😆.
std::next
method from iterator so that we can increment the iterator starting
from the begin()
returned value.newSeq
proc.The final code to (i) wrap the <list>
types and methods and (ii) one
method from <iterator>
, and (iii) define a proc for converting
std::list
objects to Nim sequences was only 22 lines! (including
comments and blank lines)
|
|
sequence = @[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
first elem = 0
elem 5 = 10
last elem = 18
You can try this code by saving to a list_to_seq.nim
file and
running nim -b:cpp r list_to_seq.nim
.
Alternatively, you can paste this code on https://play.nim-lang.org/, set the ‘Compilation target’ to C++ and hit the yellow ‘Run!’ button.
It becomes fairly easy to map any C++ (or C) library to Nim. Once that it done, you can leverage the power of Nim to extend that mapped library or manipulate data generated by that library. And you do not need to be a C++ programmer to do so (I am not).
The folks on Nim Forum, Discord and elsewhere have been very generous with their help and support, and that’s one of the main reasons it’s a joy to use Nim. 🙏
importcpp
pragmastd::list
referencecppstl
This package contains bindings to std::string
, std::vector
and
std::complex
as of .
package – can be installed using nimble install cppstl
I have almost no experience with coding in C++; I got help with
this importcpp: *#
binding from user sls1005 on this Nim Forums
thread. Thank you! ↩︎