Skip to content

Fix implicit construction in rules - #569

Merged
nunoplopes merged 36 commits into
Cpp2Rust:masterfrom
lucic71:issue-567
Oct 30, 2026
Merged

nunoplopes merged 36 commits into
Cpp2Rust:masterfrom
lucic71:issue-567

Conversation

@lucic71

@lucic71 lucic71 commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

Closes #567 and #568

C++ has 3 ways of implicitly constructing a value:

  • conversion constructor, struct Foo { Foo(int i) {} }
  • conversion operator, structure Foo { operator int() () {} }
  • integer conversions, vector(InputIt first, InputIt last) a range of longs can initialize a vector of ints

A rule that does implicit conversion cannot know which of the three versions is used. We need a common interface for all three that rules can use.

This PR adds CppFrom. Structs that have one of the two: conversion constructor or conversion operator, implement CppFrom using a derived trait. We cannot use Rust's From because From has value semantics while C++ can have reference semantics, for example: struct Foo { Foo(const OtherStructThatHasCloneSideEffects&) {} }.

struct S {
  int x;
  S(int i) : x(i) {}
};
#[derive(CppFrom)]
// Derive CppFrom for the method called `new` that receives
// the argument `i32` using `value` semantics
#[cpp_from(i32, new, value)]
pub struct S {
    pub x: i32,
}

impl S {
    pub fn new(i: i32) -> Self {
        Self { x: i }
    }
}

// expanded by the derive
impl CppFrom<i32> for S {
    fn cpp_from(src: Ptr<i32>) -> Self {
        S::new(src.read())
    }
}

Rules that need implicit construction are written like this:

// Conversion constructor of pair: pair<T3, T4> -> pair<T1, T2>
fn f24<T1: CppFrom<T3>, T2: CppFrom<T4>, T3, T4>(
    a0: (Value<T3>, Value<T4>),
) -> (Value<T1>, Value<T2>) {
    let __a0 = a0;
    (
        Rc::new(RefCell::new(T1::cpp_from(__a0.0.as_pointer()))),
        Rc::new(RefCell::new(T2::cpp_from(__a0.1.as_pointer()))),
    )
}

Some notes:

  • to optimize the generated code. I added a new Rules IR node called GenericCall. If the GenericCall is cpp_form and the source and destination are integers, don't use the fully qualified call T1::call_from(<T2 expr>), use T2 as T1
  • I replaced the Default node with GeneriCall, T1::default() is also a generic call

@nunoplopes
nunoplopes merged commit 691378b into Cpp2Rust:master Oct 30, 2026
9 checks passed
joaotgouveia pushed a commit to joaotgouveia/cpp2rust that referenced this pull request Oct 30, 2026
Closes Cpp2Rust#567 and Cpp2Rust#568

C++ has 3 ways of implicitly constructing a value:
* conversion constructor, `struct Foo { Foo(int i) {} }`
* conversion operator, `structure Foo { operator int() () {} }`
* integer conversions, `vector(InputIt first, InputIt last)` a range of
longs can initialize a vector of ints

A rule that does implicit conversion cannot know which of the three
versions is used. We need a common interface for all three that rules
can use.

This PR adds CppFrom. Structs that have one of the two: conversion
constructor or conversion operator, implement CppFrom using a derived
trait. We cannot use Rust's From because From has value semantics while
C++ can have reference semantics, for example: `struct Foo { Foo(const
OtherStructThatHasCloneSideEffects&) {} }`.

```cpp
struct S {
  int x;
  S(int i) : x(i) {}
};
```

```rust
// Derive CppFrom for the method called `new` that receives
// the argument `i32` using `value` semantics
pub struct S {
    pub x: i32,
}

impl S {
    pub fn new(i: i32) -> Self {
        Self { x: i }
    }
}

// expanded by the derive
impl CppFrom<i32> for S {
    fn cpp_from(src: Ptr<i32>) -> Self {
        S::new(src.read())
    }
}
```

---

Rules that need implicit construction are written like this:

```rs
// Conversion constructor of pair: pair<T3, T4> -> pair<T1, T2>
fn f24<T1: CppFrom<T3>, T2: CppFrom<T4>, T3, T4>(
    a0: (Value<T3>, Value<T4>),
) -> (Value<T1>, Value<T2>) {
    let __a0 = a0;
    (
        Rc::new(RefCell::new(T1::cpp_from(__a0.0.as_pointer()))),
        Rc::new(RefCell::new(T2::cpp_from(__a0.1.as_pointer()))),
    )
}
```

---

Some notes:
* to optimize the generated code. I added a new Rules IR node called
GenericCall. If the GenericCall is cpp_form and the source and
destination are integers, don't use the fully qualified call
`T1::call_from(<T2 expr>)`, use `T2 as T1`
* I replaced the Default node with GeneriCall, T1::default() is also a
generic call
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Incorrect std::pair forwarding constructor rule

2 participants