header.hpp
namespace outer {
namespace inner {
class MyClass {
public:
short attr1;
};
}
}
This works:
type
MyClass {.header: "myheader.hpp", importcpp: "outer::inner::MyClass".} = object
attr1: int16
This goes error: ‘outer’ has not been declared:
type
MyClass {.importcpp: "outer::inner::MyClass".} = object
attr1: int16
Is it possible to avoid using header pragma when using importcpp (like importc)?
@giaco if you are in devel you will soon be able (hopefully) to specify your own replacement for nimbase.h so you can add your base types there. If not, wait for 2.1 I guess. In the meantime you can use a custom pragma that wraps header if it bothers you too much or just fork nimbase.h and add your includes there.
@arnetheduck hope that's a bad joke and you are trolling around :P
@arnetheduck hope that's a bad joke and you are trolling around :P
not really - the semantic mismatch between nim and (modern) C++ is significant, starting with trivial basic stuff like r-value refs and const that have no semantic equivalent in Nim - on the other hand, the one thing that practically all languages have a agreed on is that C is a decent-enough language for interop and plenty of C++ libraries offer a C API on their own because this makes them easy to integrate anywhere, Nim included. There still exists a semantic gap between Nim and C, but that gap is much smaller and much easier to bridge.
Therefore, if you're writing a wrapper, it will sooner or later hit a wall where you'll have to reach for a simplified API that you'll construct, except in the case that "C++-light" was used by the library you're wrapping, ie the it was written from start with minimal use of actual features that C++ provides on top of C. Notably, a few game engines and other long-lasting relics of C++98 are written in this conservative way.
Viewed from a Nim-centric point of view, adding more and more workarounds and patches and semi-features to hobble together partial support for a few more % of C++ constrains Nim itself in that it removes maneuverability in terms of innovation and unique features, because now whatever semi-working C++ support there is must be taken into consideration for backwards-compatibility, so this is not a "free" approach for Nim to take.
All that said, I know you've come far with uefornim - congrats! However, sometimes, if what you're looking for is a pragmatic solution to make things work, a small wrapper written in C++ that exposes a more simple-to-consume facade is the way to go and will be less work, more future-proof and with less grief and complexity than trying to cram everything into Nim.
a small wrapper written in C++ that exposes a more simple-to-consume facade
Which is not C. You are still using the C++ backend :P
edit: "wait for nim 2.1" is a good example of the above: it means that you cannot be productive in nim-C++ wrapping for the next 2 years
This can be applied to C too, as it improves the C situation as well. For instance, as things are Today, you cant precompile nimbase.h if you want to precompile something else. Plus the simplified wrapper wouldnt solve the issue as you still need to add it to the header.
the semantic mismatch between nim and (modern) C++ is significant, starting with trivial basic stuff like r-value refs and const that have no semantic equivalent in Nim
I would fully agree here if Nim didnt have macros. Im all for codegenDecl because it can move most interop problems (except things like virtual) to user space. In other words, you can have a macro that does the heavy work for you and transparent to the user. For instance, see this one that allows the user to specify const params, and calls to super without further modifications to the lang and also taking care of the this being const for you, so you dont have to worry about semantics missmatches:
A copy paste form NimForUE:
func removeConst*[T](p:ptr T) : ptr T {.importcpp: "const_cast<'0>(#)".}
func generateSuper(procDef: NimNode, parentName: string) : NimNode =
let name = procDef.name.strVal.capitalizeAscii()
let parent = procDef.params[1]
let content = newLit &"{parentName}::{name}"
result =
genAst(content):
proc super() {.importc: content, nodecl.}
result.params = nnkFormalParams.newTree procDef.params.filterIt(it != parent)
func processVirtual(procDef: NimNode, parentName: string) : NimNode =
#[
if the proc has virtual, it will fill it with the proc info:
- Capitilize the proc name
- Add const if the proc is const, same with params
- Add override if the proc is marked with override
Get rid of the pragmas.
If the proc has any content in virtual, it will ignore the pragma and use the content instead
]#
let isPlainVirtual = (it:NimNode) => it.kind == nnkIdent and it.strVal() == "virtual"
let isOverride = (it:NimNode) => it.kind == nnkIdent and it.strVal() == "override"
let isConstCpp = (it:NimNode) => it.kind == nnkIdent and it.strVal() == "constcpp"
let isParamConstCpp = (it:NimNode) => it.kind == nnkIdentDefs and it[0].kind == nnkPragmaExpr and
it[0][^1].children.toSeq.any(isConstCpp)
let constParamContent = (it:NimNode) => (if isParamConstCpp(it): "const " else: "")
let hasVirtual = procDef.pragma.toSeq.any(isPlainVirtual) #with content it will be differnt. But we are ignoring it anyways
result = procDef
if not hasVirtual:
return procDef
let hasOverride = procDef.pragma.toSeq.any(it=>it.kind == nnkIdent and it.strVal() == "override")
let hasFnConstCpp = procDef.pragma.toSeq.any(isConstCpp)
let name = procDef.name.strVal().capitalizeAscii()
let params = procDef
.params
.filterIt(it.kind == nnkIdentDefs)
.skip(1)
.mapi((n, idx) => "$1 '$2 #$2" % [constParamContent(n), $(idx + 2)])
.join(", ")
let override = if hasOverride: "override" else: ""
let fnConstCpp = if hasFnConstCpp: "const" else: ""
let virtualContent: string = &"{name}({params}) {fnConstCpp} {override}"
let keptPragmas = procDef.pragma.toSeq
.filterIt(not @[isPlainVirtual(it), isOverride(it), isConstCpp(it)].foldl(a or b, false))
let newVirtual = nnkExprColonExpr.newTree(ident "virtual", newLit virtualContent)
let pragmas = nnkPragma.newTree(keptPragmas & newVirtual)
if params.len > 0:
var params = newSeq[NimNode]()
for param in procDef.params[2..^1]:
var param = param
if isParamConstCpp(param):
param[0][^1] = nnkPragma.newTree param[0][^1].children.toSeq.filterIt(not isConstCpp(it))
params.add param
result[3] = nnkFormalParams.newTree(procDef.params[0..1] & params)
result.pragma = pragmas
result.body.insert 0, generateSuper(procDef, parentName)
if hasFnConstCpp:
let selfNoConst =
genAst():
let self {.inject.} = removeConst(self)
result.body.insert 0, selfNoConst
Which is not C. You are still using the C++ backend :P
I misread your comment, I thought you were referring to what some people in the community is doing by picking the hard path of wrapping something that can easily be done in C++, in C.
That said, the small facades are the way to go sometimes with any backend really, from C++ to JS.