Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cannot use .importcpp type with non-public default constructor as a return type of a proc #4687

Open
endragor opened this issue Aug 31, 2016 · 10 comments

Comments

@endragor
Copy link
Contributor

Because Nim codegen declares result variable at the beginning of a proc, it always tries to invoke the object's default constructor when compiling to C++ backend. But, when dealing with wrappers (UE4 in my case), some imported types don't have the default constructor, and so it's impossible to define Nim procedure that returns a value of that type.

@vegansk
Copy link
Contributor

vegansk commented Aug 31, 2016

Ugly workaround:

{.emit:"""
class Test {
private:
  Test() {}
public:
  Test(int x) {}
};
"""}

type
  Test {.importcpp: "Test", nodecl.} = object

proc initTest(x: cint): Test {.importcpp: "Test(@)", nodecl.}

proc mkTest(x: int): Test {.asmNoStackFrame.} =
  {.emit: "return Test(`x`);".}

@endragor
Copy link
Contributor Author

@vegansk
Thanks for the suggestion! Trying to use asmNoStackFrame hasn't come to my mind. But that doesn't compile with clang++:

error: non-ASM statement in naked function is not supported
        return Test(x0);
        ^

(Nim codegen declares mkTest as naked)

@vegansk
Copy link
Contributor

vegansk commented Aug 31, 2016

@endragor
Copy link
Contributor Author

@vegansk Do you mean returning pointer instead of the object? Unfortunately, that's not an option for me, I have to mimic signature as required by the framework. If there was a way to force Nim to not generate result variable and return statement, beside asmNoStackFrame, that would help.

@Araq
Copy link
Member

Araq commented Sep 3, 2016

You can declare the proc as .noInit and then no result initialization is generated. Ah, but that still declares the variable. Will fix it for C++.

@endragor
Copy link
Contributor Author

endragor commented Sep 7, 2016

@Araq the change for .noinit doesn't work when there is conditional return in the middle of a proc:

type
  Vector {.importcpp: "std::vector", header: "vector".} [T] = object

proc initVector[T](): Vector[T] {.importcpp: "std::vector<'*0 >()", header: "vector", constructor.}
proc add[T](vec: Vector[T], item: T)  {.importcpp: "push_back", header: "vector".}

proc initVectorWithItem[T](item: T): Vector[T] {.noinit.} =
  result = initVector[T]()
  if false:
    return
  result.add(item)

discard initVectorWithItem(1)

@Araq
Copy link
Member

Araq commented Sep 7, 2016

Yeah well, it's hard to support it without rewriting the codegen. C++ hates labels and goto and a codegenerator loves it.

@stale
Copy link

stale bot commented Aug 4, 2020

This issue has been automatically marked as stale because it has not had recent activity. If you think it is still a valid issue, write a comment below; otherwise it will be closed. Thank you for your contributions.

@stale stale bot added the stale Staled PR/issues; remove the label after fixing them label Aug 4, 2020
@mratsim mratsim removed the stale Staled PR/issues; remove the label after fixing them label Jan 10, 2021
mratsim added a commit to SciNim/flambeau that referenced this issue Jan 10, 2021
- workaround a disappearing typedesc arg
- workaround no-default constructor: nim-lang/Nim#4687
mratsim added a commit to SciNim/flambeau that referenced this issue Jan 10, 2021
@jmgomez
Copy link
Collaborator

jmgomez commented Sep 16, 2023

Now there is a workaround for most of the cases: https://nim-lang.github.io/Nim/manual_experimental.html#constructor-initializer

@demotomohiro
Copy link
Contributor

Similar problem happens when the imported C++ class doesn't have a copy constructor or a copy assignment operator.

uncopyable.hpp:

#pragma once

#include <iostream>

using namespace std;

struct Uncopyable {
  Uncopyable() {
    cout << "Uncopyable()\n";
  }

  Uncopyable(int x): x(x) {
    cout << "Uncopyable(" << x << ")\n";
  }

  Uncopyable(const Uncopyable&) = delete;
  Uncopyable& operator = (const Uncopyable&) = delete;

  void print() {
    cout << "Uncopyable::print: " << x << endl;
  }

  int x;
};

testnocpy.cpp

#include "uncopyable.hpp"

Uncopyable initUncopyableInCpp(int x) {
  return Uncopyable(x);
}

int main() {
  auto a = initUncopyableInCpp(123);
  a.print();

  return 0;
}

Compile and run:

$ g++ testnocpy.cpp 
$ ./a.out 
Uncopyable(123)
Uncopyable::print: 123

Copy constructor and copy assignment operator of Uncopyable are deleted but you can create a function returns an instance of it.
It is because Copy elision:
https://en.cppreference.com/w/cpp/language/copy_elision

Following Nim code imports Uncopyable struct and implements a proc similar to initUncopyableInCpp.
testnocpy.nim:

type
  Uncopyable {.header: "uncopyable.hpp".} = object
    x: cint

proc constructUncopyable(x: cint): Uncopyable {.header: "uncopyable.hpp", importcpp: "Uncopyable(@)", constructor.}
proc print(x: Uncopyable) {.header: "uncopyable.hpp", importcpp.}

proc initUncopyableInNim(x: cint): Uncopyable =
  constructUncopyable(x)

proc main =
  let a = initUncopyableInNim(5)
  a.print()

main()
$ nim cpp -r -f testnocpy.nim 
CC: testnocpy.nim
@mtestnocpy.nim.cpp: In function ‘Uncopyable initUncopyableInNim__testnocpy_u7(int)’:
@mtestnocpy.nim.cpp:70:49: Error: use of deleted function ‘Uncopyable& Uncopyable::operator=(
const Uncopyable&)’
   70 |         nimln_(9);      result = Uncopyable(x_p0);
      |                                                 ^

initUncopyableInNim proc looks almost same to initUncopyableInCpp but it result in a compile error.
This error can be fixed by removing following lines in uncopyable.hpp

uncopyable.hpp:

  // Uncopyable(const Uncopyable&) = delete;
  // Uncopyable& operator = (const Uncopyable&) = delete;

or implementing them:

  Uncopyable(const Uncopyable& src): x(src.x) {
    cout << "Uncopyable(const Uncopyable&)\n";
  }

  Uncopyable& operator = (const Uncopyable& src) {
    cout << "Copy Uncopyable\n";
    x = src.x;
    return *this;
  }
$ nim cpp -r -f testnocpy.nim 
Uncopyable()
Uncopyable(5)
Copy Uncopyable
Uncopyable(const Uncopyable&)
Uncopyable::print: 5

But it calls default constructor and copy constructor. In testnocpy.cpp, they ware not called.
If constructor just sets values to member variables, extra constructor call would not be a problem as unnecessary assignments are removed by optimizer.
But constructors can have side effects like, logging, IO operation, create a window or other OS API calls.
These side effects are not optimized and removed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

7 participants