Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Enhancements:
* Add support for `noexcept` functions, static members, and static member functions
* Add support for `Buffer<void*>` and `Pointer<void*>`
* Add support for `std::function`. Ruby procs, lambdas, and blocks can be wrapped in `std::function` objects and passed to C++ methods. C++ functions returning `std::function` are automatically wrapped.
* Add support for `std::ostream`, `std::ostringstream`, and `std::ofstream`. Ruby can write to C++ streams and pass them to C++ functions. Standard streams are exposed as `Std::COUT` and `Std::CERR`.

Internal:
* Refactor type handling by merging `TypeMapper` into `TypeDetail` and simplifying class hierarchy
Expand Down
189 changes: 189 additions & 0 deletions docs/architecture/incomplete_types.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
# Incomplete Types

Rice supports working with incomplete (forward-declared) types. This is essential for wrapping C++ libraries that use the [PIMPL](https://en.cppreference.com/w/cpp/language/pimpl.html) idiom or [opaque](https://en.wikipedia.org/wiki/Opaque_pointer) handle patterns, where implementation details are hidden behind forward declarations.

## What Are Incomplete Types?

An incomplete type is a type that has been declared but not defined. The compiler knows the type exists but doesn't know its size or layout:

```cpp
// Forward declaration - incomplete type
class WidgetImpl;

class Widget {
public:
// Returns pointer to incomplete type
WidgetImpl* getImpl();

// Returns reference to incomplete type
WidgetImpl& getImplRef();

private:
WidgetImpl* pImpl_; // PIMPL idiom
};
```

## Common Patterns

### PIMPL Idiom

The Private Implementation (PIMPL) idiom hides implementation details:

```cpp
// widget.hpp - public header
class WidgetImpl; // Forward declaration only

class Widget {
public:
Widget();
~Widget();
void doSomething();
WidgetImpl* getImpl();

private:
WidgetImpl* pImpl_;
};

// widget.cpp - implementation (not exposed to users)
class WidgetImpl {
int value = 42;
// ... implementation details
};
```

### Opaque Handles

Common in C libraries where handles are passed around without exposing internals:

```cpp
// Forward declaration only - never defined in headers
struct OpaqueHandle;

OpaqueHandle* createHandle();
void destroyHandle(OpaqueHandle* handle);
int getHandleData(OpaqueHandle* handle);
```

## Wrapping Incomplete Types

To wrap functions that use incomplete types, you must register the incomplete type with Rice using `define_class`. Rice doesn't need to know the type's size or layout - it just needs to know the type exists:

```cpp
#include <rice/rice.hpp>

// Forward declaration - WidgetImpl is never defined here
class WidgetImpl;

class Widget {
public:
WidgetImpl* getImpl();
WidgetImpl& getImplRef();
};

extern "C" void Init_widget()
{
using namespace Rice;

// Register the incomplete type - Rice just needs to know it exists
define_class<WidgetImpl>("WidgetImpl");

// Now we can wrap methods that use WidgetImpl
define_class<Widget>("Widget")
.define_constructor(Constructor<Widget>())
.define_method("get_impl", &Widget::getImpl)
.define_method("get_impl_ref", &Widget::getImplRef);
}
```

## Supported Operations

Rice supports incomplete types in the following contexts:

| Operation | Example | Supported |
|---------------------------|----------------------------------|-----------|
| Return pointer | `Impl* getImpl()` | Yes |
| Return reference | `Impl& getImplRef()` | Yes |
| Return const reference | `const Impl& getImplRef() const` | Yes |
| Parameter pointer | `void setImpl(Impl* impl)` | Yes |
| Parameter reference | `void process(Impl& impl)` | Yes |
| Parameter const reference | `void read(const Impl& impl)` | Yes |

## Error Handling

If you try to use an incomplete type without registering it, Rice will raise a Ruby exception:

```cpp
// Without registering OpaqueHandle...
define_global_function("create_handle", &createHandle);

// Calling from Ruby will raise:
// Rice::Exception: Type is not registered with Rice: OpaqueHandle
```

Always register incomplete types with `define_class` before wrapping functions that use them.

## Real-World Example: OpenCV

OpenCV uses the PIMPL idiom extensively. For example, `cv::dnn::Net` has:

```cpp
namespace cv { namespace dnn {
class Net {
public:
struct Impl; // Forward declaration

Impl* getImpl() const;
Impl& getImplRef();

private:
Ptr<Impl> impl;
};
}}
```

To wrap this in Rice:

```cpp
#include <rice/rice.hpp>
#include <opencv2/dnn.hpp>

extern "C" void Init_dnn()
{
using namespace Rice;

Module rb_mCv = define_module("Cv");
Module rb_mDnn = define_module_under(rb_mCv, "Dnn");

// Register the incomplete Impl type
define_class_under<cv::dnn::Net::Impl>(rb_mDnn, "Impl");

// Now wrap Net with its getImpl methods
define_class_under<cv::dnn::Net>(rb_mDnn, "Net")
.define_constructor(Constructor<cv::dnn::Net>())
.define_method("get_impl", &cv::dnn::Net::getImpl)
.define_method("get_impl_ref", &cv::dnn::Net::getImplRef);
}
```

## How It Works

Rice handles incomplete types by:

1. Storing a pointer to the object without needing to know its size
2. Using type erasure to manage the Ruby VALUE wrapper
3. Tracking the type in Rice's type registry for proper conversion

Since Rice only stores pointers and never copies incomplete types by value, it doesn't need the complete type definition. The actual object lifetime is managed by the C++ code that owns it.

## Limitations

- Cannot create instances of incomplete types from Ruby (no constructor)
- Cannot copy incomplete types by value
- Cannot access members of incomplete types directly
- The incomplete type must be registered before any function using it is called

## See Also

- [Pointers](../bindings/pointers.md) - General pointer handling in Rice
- [References](../bindings/references.md) - Reference handling in Rice
- [Memory Management](../bindings/memory_management.md) - Object lifetime management
125 changes: 125 additions & 0 deletions docs/stl/ostream.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
# std::ostream

Rice provides support for exposing C++ output streams to Ruby. This allows Ruby code to interact with C++ streams, including writing to them and passing them to C++ functions that expect stream arguments.

## Supported Classes

Rice supports three output stream types:

* `std::ostream` - The base output stream class
* `std::ostringstream` - String-based output stream
* `std::ofstream` - File-based output stream

## Ruby Classes

The stream classes are defined in the `Std` module:

* `Std::OStream` - Base class for output streams
* `Std::OStringStream` - Inherits from `Std::OStream`
* `Std::OFStream` - Inherits from `Std::OStream`

## Defining Stream Classes

To explicitly define the stream classes:

```cpp
#include <rice/rice.hpp>
#include <rice/stl.hpp>

Rice::define_ostream(); // Defines Std::OStream
Rice::define_ostringstream(); // Defines Std::OStringStream
Rice::define_ofstream(); // Defines Std::OFStream
```

Stream classes are also automatically registered when C++ functions use them as parameters or return types.

## Standard Stream Constants

When `define_ostream()` is called, Rice also defines constants for the standard C++ streams:

* `Std::COUT` - References `std::cout`
* `Std::CERR` - References `std::cerr`

## Ruby API

### OStream Methods

All output stream classes inherit these methods from `Std::OStream`:

| Method | Description |
|:-------|:------------|
| `write(value)` | Writes a value to the stream |
| `<<` | Alias for `write` |
| `flush` | Flushes the stream buffer |
| `clear` | Clears error state flags |
| `good?` | Returns true if no error flags are set |
| `bad?` | Returns true if a non-recoverable error occurred |
| `fail?` | Returns true if an operation failed |
| `eof?` | Returns true if end-of-file was reached |

### OStringStream Methods

In addition to inherited methods:

| Method | Description |
|:-------|:------------|
| `new` | Creates an empty string stream |
| `new(str)` | Creates a stream initialized with the given string |
| `str` | Returns the stream contents as a string |
| `str=` | Sets the stream contents |
| `to_s` | Alias for `str` |

### OFStream Methods

In addition to inherited methods:

| Method | Description |
|:-------|:------------|
| `new` | Creates an unopened file stream |
| `new(filename)` | Creates and opens a file stream |
| `open(filename)` | Opens a file for writing |
| `close` | Closes the file |
| `open?` | Returns true if a file is open |

## Examples

### Writing to a String Stream

```ruby
stream = Std::OStringStream.new
stream.write("Hello")
stream << " " << "World" << 123
puts stream.str # => "Hello World123"
```

### Writing to a File

```ruby
stream = Std::OFStream.new("output.txt")
stream << "Line 1\n"
stream << "Line 2\n"
stream.flush
stream.close
```

### Passing Streams to C++ Functions

```cpp
// C++ code
void logMessage(std::ostream& os, const std::string& msg) {
os << "[LOG] " << msg << std::endl;
}

// Register with Rice
m.define_module_function("log_message", &logMessage);
```

```ruby
# Ruby code
stream = Std::OStringStream.new
log_message(stream, "Application started")
puts stream.str # => "[LOG] Application started\n"

# Or write directly to stdout
log_message(Std::COUT, "Hello from Ruby!")
```
3 changes: 3 additions & 0 deletions docs/stl/stl.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ Wrapped classes include:
* std::function
* std::map
* std::multimap
* std::ofstream
* std::ostream
* std::ostringstream
* std::pair
* std::set
* std::shared_ptr
Expand Down
2 changes: 1 addition & 1 deletion rice/Data_Type.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@ namespace Rice
}
else
{
// This gives a chance for to auto-register classes such as std::exception
// This gives a chance to auto-register classes such as std::exception
detail::verifyType<Base_T>();
result = Data_Type<Base_T>::klass();
}
Expand Down
4 changes: 3 additions & 1 deletion rice/stl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@
#include "stl/complex.hpp"
#include "stl/filesystem.hpp"
#include "stl/optional.hpp"
#include "stl/reference_wrapper.hpp"
#include "stl/ios_base.hpp"
#include "stl/ostream.hpp"
#include "stl/pair.hpp"
#include "stl/reference_wrapper.hpp"
#include "stl/map.hpp"
#include "stl/monostate.hpp"
#include "stl/multimap.hpp"
Expand Down
13 changes: 13 additions & 0 deletions rice/stl/ios_base.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#ifndef Rice__stl__ios_base__hpp_
#define Rice__stl__ios_base__hpp_

#include <ios>

namespace Rice
{
Data_Type<std::ios_base> define_ios_base();
}

#include "ios_base.ipp"

#endif // Rice__stl__ios_base__hpp_
Loading