Skip to content

ArnavT005/cstring-lite

cstring-lite

Linux macOS Windows License: MIT

A lightweight C++ header-only library for safe and efficient interoperation with C APIs that either accept or return C-style strings.

Motivation

C++ codebases often find themselves using C APIs for certain functionalities. These APIs can sometimes accept a null-terminated const char* string (non-owning) as an input argument, or return a heap-allocated char* string (owning) as their output, or both. The current C++ standard defines std::string_view and std::string classes to represent non-owning and owning string types respectively. However, these do not always safely or efficiently interoperate with C APIs:

  • A std::string_view object cannot always be safely passed to a C API accepting a null-terminated const char* string since the former does not guarantee null-termination.

Note

std::string type does provide the null-termination guarantee, but it's an owning type and can lead to unnecessary allocations if not used carefully.

  • On the other hand, a std::string object cannot be constructed from a heap-allocated char* string returned by a C API without performing a deep copy. This double allocation can sometimes be undesirable, especially when the intention is only to perform string operations on the returned value.

Note

std::string_view type can be constructed from the returned char* string to facilitate string operations, but it's a non-owning type and can lead to a memory leak if not used carefully.

Therefore, there is a need for string types that can bridge the gap between standard C++ strings and raw C-style strings for better interoperation.

Bridge Types

To address the challenges outlined above, this repository introduces two types: tuli::cstring_view and tuli::cstring. These types are designed to safely and efficiently bridge the gap between C++ and C string semantics, making interoperation seamless and less error-prone.

tuli::cstring_view

A lightweight, non-owning view over a null-terminated C-style string. Unlike std::string_view, tuli::cstring_view guarantees null-termination, making it safe to pass directly to C APIs expecting a null-terminated const char* string. It can be constructed from a const char*, std::string, or a null-terminated std::string_view object, and provides utility methods to get the length, check for emptiness, and access the underlying C-style string.

Key features:

  • Guarantees null-termination for safe C API interop
  • Non-owning, zero-overhead abstraction
  • Provides length(), is_empty(), and c_str() utility methods
  • Implicit conversion to std::string_view for C++ compatibility
  • Stream output support via operator<<
  • User-defined literal operator (e.g. "example"_csv) for convenient construction

template <class Deleter> tuli::cstring

An owning, RAII-enabled wrapper for heap-allocated C-style strings. This type takes ownership of the char* string returned by a C API (including nullptr if the API signals failure that way), manages its lifetime, and ensures proper deallocation upon scope exit. It avoids unnecessary deep copies and memory leaks, while still allowing string operations via implicit conversion to std::string_view.

Key features:

  • Owns and manages the lifetime of a heap-allocated char* string
  • Move-only semantics to prevent accidental copies
  • Provides length(), is_empty(), is_null(), and c_str() utility methods
  • Implicit conversion to std::string_view for C++ compatibility
  • Stream output support via operator<<
  • Ensures memory is freed automatically, preventing leaks

Note

If unspecified, tuli::default_delete is used as the default Deleter. Internally, this calls std::free() on the allocated char* string upon scope exit, making it suitable for memory allocated using standard C allocators such as malloc, calloc, or realloc.

Tip

Some C APIs also provide custom deallocator methods to free previously allocated char* strings. To enable proper cleanup in such cases, a custom deleter type can be passed as the template parameter when using tuli::cstring. The requirements for such a deleter are the same as those imposed by the std::unique_ptr type.

Requirements

  • A C++ compiler with C++17 support or higher
  • No external dependencies

Compatibility

Tested on the following platforms and compilers:

Platform Compiler C++ Standards Compiler Flags
Linux GCC, Clang C++17, C++20, C++23 -Wall -Wextra -Werror -Wpedantic
macOS Apple Clang C++17, C++20, C++23 -Wall -Wextra -Werror -Wpedantic
Windows MSVC C++17, C++20, C++23 /W4 /WX /permissive-

Installation

Just copy the files present in include/ directory to your repository's include directory and you are ready to go! No separate compiling/linking required.

Running Tests

This project uses GoogleTest to write and run tests. All test cases are located in the tests/ directory. To run the test cases, follow the steps given below:

Important

Prerequisites:

  • CMake version 3.14 or higher
  • A C++ compiler with C++17 support
  1. Clone the repository
git clone https://github.com/ArnavT005/cstring-lite.git
cd cstring-lite
  1. Build project with CMAKE_BUILD_TESTS CMake option set to TRUE
cmake -B build -DCMAKE_BUILD_TESTS=TRUE -S .
cmake --build build
  1. Run the tests

You can run the tests using CTest:

cd build
ctest --test-dir tests/

Or run the test binary directly:

./build/tests/tests

Note

GoogleTest is automatically downloaded by CMake during the first build, so no manual setup is required.

Usage Examples

Sample examples are present in the examples/ directory. The following snippets illustrate the usage scenarios:

  • Using tuli::cstring_view for making safe and efficient C API wrappers
#include <cstdlib>
#include <string>
#include <string_view>

#include <tuli/cstring_view.hpp>

extern int some_c_api(const char* str); // returns -ve value on failure

[[nodiscard]] int some_c_api_wrapper(tuli::cstring_view csv) noexcept {
  return some_c_api(csv.c_str());
}

using std::operator""s;
using std::operator""sv;

int main() {
  const auto s{"C-style string"};
  const auto sv{"std::string_view (null-terminated)"sv};
  const auto str{"std::string"s};

  if (some_c_api_wrapper(s) < 0 ||
      some_c_api_wrapper({tuli::null_terminated, sv}) < 0 ||
      some_c_api_wrapper(str) < 0 ) {
        return EXIT_FAILURE;
  }

  return EXIT_SUCCESS;
}
  • Using tuli::cstring to safely and efficiently wrap heap-allocated char* string and use it as a string type
#include <string_view>

#include <tuli/cstring.hpp>
#include <tuli/cstring_view.hpp>

extern char* some_c_api(const char* str); // returns malloced duplicated 'str'

[[nodiscard]] tuli::cstring<> some_c_api_wrapper(tuli::cstring_view csv) noexcept {
  return {tuli::owned, some_c_api(csv.c_str())};
}

[[nodiscard]] bool some_cpp_api(std::string_view sv) noexcept {
    const auto result_1{sv == "std::string_view"};
    const auto result_2{sv.substr(0, 3) == "std"};
    const auto result_3{sv.find("str") != std::string_view::npos};

    return (result_1 && result_2 && result_3);
}

using std::operator""sv;

int main() {
    const auto sv{"std::string_view"sv};
    const auto cstr{some_c_api_wrapper({tuli::null_terminated, sv})};

    return some_cpp_api(cstr) ? EXIT_SUCCESS : EXIT_FAILURE;
}

Running Examples

To build and run examples, follow the steps given below: (for prerequisites and cloning instructions, see Running Tests section)

  1. Build project with CMAKE_BUILD_EXAMPLES CMake option set to TRUE
cmake -B build -DCMAKE_BUILD_EXAMPLES=TRUE -S .
cmake --build build
  1. Run the examples
./build/examples/examples

Expected output:

C-style string says hello!
std::string_view (null-terminated) says hello!
std::string says hello!
Examples ran successfully.

Related Works and Discussion

cstring_view

A number of proposals have been made to introduce a null-terminated cstring_view type in the C++ standard library. These include the following:

  • P1402R0: std::cstring_view - a C compatible std::string_view adapter (rejected)
  • P3655R3: std::cstring_view (proposed for inclusion in C++29)

Implementations for the same can also be found on GitHub here and here. Though these solve the null-termination issue in similar ways, this library takes a more minimal approach — most string operations are delegated to std::string_view via implicit conversion, keeping cstring_view focused on its primary role at the C/C++ boundary rather than serving as a general-purpose string type.

cstring

tuli::cstring<Deleter> is essentially a one-to-one wrapper over std::unique_ptr<char, Deleter>, so a std::unique_ptr can, in theory, replace the usage of tuli::cstring. However, tuli::cstring offers some semantic advantages over std::unique_ptr:

  • tuli::cstring makes the programmer's intention clear to the reader and is more expressive. std::unique_ptr implementation can possibly lead to errors if not used carefully.
  • tuli::cstring provides implicit conversion to std::string_view, which makes performing string operations on the returned char* string smoother and less error-prone.

About

A lightweight C++ header-only library for safe and efficient interoperation with C APIs that either accept or return C-style strings.

Topics

Resources

License

Code of conduct

Contributing

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors