This is an automated email from the ASF dual-hosted git repository.

paleolimbot pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow-nanoarrow.git


The following commit(s) were added to refs/heads/main by this push:
     new 53202371 refactor: Split up nanoarrow.hpp into multiple .hpp files 
(#668)
53202371 is described below

commit 5320237165c2a88853525020f4776952c665211e
Author: Dewey Dunnington <[email protected]>
AuthorDate: Fri Nov 1 20:54:14 2024 +0000

    refactor: Split up nanoarrow.hpp into multiple .hpp files (#668)
    
    This PR splits `nanoarrow.hpp`, which was getting large, into multiple
    files (bundling them, of course, when creating the bundled
    distribution). This is intended to make space for new helpers that are
    being proposed in #599 although I think it's also just a good idea in
    general since the utilities to create streams and buffers using
    templating are in a pretty different category from the things that keep
    you from leaking memory and many users will only need part of the API.
    Because it's header-only on top of the C runtime, I think this is
    probably a good pattern to enable.
    
    This doesn't quite solve `nanoarrow_device.hpp` and
    `nanoarrow_ipc.hpp`...they currently still just include all of
    `nanoarrow.hpp`. I think we can probably solve that in the bundler but I
    might punt on that since it's not actually hurting anything at the
    moment.
---
 CMakeLists.txt                                     |  48 +-
 ci/scripts/bundle.py                               |  23 +-
 meson.build                                        |  27 +-
 src/nanoarrow/common/nanoarrow_hpp_test.cc         | 415 ----------
 src/nanoarrow/hpp/array_stream.hpp                 | 204 +++++
 src/nanoarrow/hpp/array_stream_test.cc             |  64 ++
 src/nanoarrow/hpp/buffer.hpp                       |  85 ++
 src/nanoarrow/hpp/buffer_test.cc                   |  86 ++
 src/nanoarrow/hpp/exception.hpp                    |  79 ++
 .../exception_test.cc}                             |  24 +-
 src/nanoarrow/hpp/operators.hpp                    |  62 ++
 src/nanoarrow/hpp/unique.hpp                       | 226 +++++
 src/nanoarrow/hpp/unique_test.cc                   | 182 ++++
 src/nanoarrow/hpp/view.hpp                         | 377 +++++++++
 src/nanoarrow/hpp/view_test.cc                     | 137 +++
 src/nanoarrow/nanoarrow.hpp                        | 921 +--------------------
 src/nanoarrow/nanoarrow_config.h.in                |  11 +-
 17 files changed, 1611 insertions(+), 1360 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 04419839..4cefb8fb 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -223,6 +223,13 @@ if(NANOARROW_IPC)
   install(TARGETS nanoarrow_ipc DESTINATION lib)
   install(FILES src/nanoarrow/nanoarrow_ipc.h src/nanoarrow/nanoarrow_ipc.hpp
                 src/nanoarrow/ipc/flatcc_generated.h DESTINATION 
include/nanoarrow)
+  install(FILES src/nanoarrow/hpp/array_stream.hpp
+                src/nanoarrow/hpp/buffer.hpp
+                src/nanoarrow/hpp/exception.hpp
+                src/nanoarrow/hpp/operators.hpp
+                src/nanoarrow/hpp/unique.hpp
+                src/nanoarrow/hpp/view.hpp
+          DESTINATION include/nanoarrow/hpp)
 endif()
 
 if(NANOARROW_IPC AND (NANOARROW_BUILD_INTEGRATION_TESTS OR 
NANOARROW_BUILD_TESTS))
@@ -444,11 +451,16 @@ if(NANOARROW_BUILD_TESTS)
   add_executable(array_test src/nanoarrow/common/array_test.cc)
   add_executable(schema_test src/nanoarrow/common/schema_test.cc)
   add_executable(array_stream_test src/nanoarrow/common/array_stream_test.cc)
-  add_executable(nanoarrow_hpp_test src/nanoarrow/common/nanoarrow_hpp_test.cc)
   add_executable(nanoarrow_testing_test src/nanoarrow/testing/testing_test.cc)
   add_executable(c_data_integration_test
                  src/nanoarrow/integration/c_data_integration_test.cc)
 
+  add_executable(hpp_array_stream src/nanoarrow/hpp/array_stream_test.cc)
+  add_executable(hpp_buffer src/nanoarrow/hpp/buffer_test.cc)
+  add_executable(hpp_exception src/nanoarrow/hpp/exception_test.cc)
+  add_executable(hpp_unique src/nanoarrow/hpp/unique_test.cc)
+  add_executable(hpp_view src/nanoarrow/hpp/view_test.cc)
+
   target_link_libraries(utils_test
                         nanoarrow_testing
                         gtest_main
@@ -472,17 +484,37 @@ if(NANOARROW_BUILD_TESTS)
                         gtest_main
                         gmock_main
                         nanoarrow_coverage_config)
-  target_link_libraries(nanoarrow_hpp_test
+  target_link_libraries(nanoarrow_testing_test nanoarrow_testing gtest_main
+                        nanoarrow_coverage_config)
+  target_link_libraries(c_data_integration_test
+                        nanoarrow
+                        nanoarrow_c_data_integration
+                        gtest_main
+                        nanoarrow_coverage_config)
+  target_link_libraries(hpp_array_stream
                         nanoarrow
                         gtest_main
                         gmock_main
                         nanoarrow_coverage_config)
-  target_link_libraries(nanoarrow_testing_test nanoarrow_testing gtest_main
+  target_link_libraries(hpp_buffer
+                        nanoarrow
+                        gtest_main
+                        gmock_main
                         nanoarrow_coverage_config)
-  target_link_libraries(c_data_integration_test
+  target_link_libraries(hpp_exception
+                        nanoarrow
+                        gtest_main
+                        gmock_main
+                        nanoarrow_coverage_config)
+  target_link_libraries(hpp_unique
                         nanoarrow
-                        nanoarrow_c_data_integration
                         gtest_main
+                        gmock_main
+                        nanoarrow_coverage_config)
+  target_link_libraries(hpp_view
+                        nanoarrow
+                        gtest_main
+                        gmock_main
                         nanoarrow_coverage_config)
 
   include(GoogleTest)
@@ -494,9 +526,13 @@ if(NANOARROW_BUILD_TESTS)
   gtest_discover_tests(array_test DISCOVERY_TIMEOUT 10)
   gtest_discover_tests(schema_test DISCOVERY_TIMEOUT 10)
   gtest_discover_tests(array_stream_test DISCOVERY_TIMEOUT 10)
-  gtest_discover_tests(nanoarrow_hpp_test DISCOVERY_TIMEOUT 10)
   gtest_discover_tests(nanoarrow_testing_test DISCOVERY_TIMEOUT 10)
   gtest_discover_tests(c_data_integration_test DISCOVERY_TIMEOUT 10)
+  gtest_discover_tests(hpp_array_stream)
+  gtest_discover_tests(hpp_buffer)
+  gtest_discover_tests(hpp_exception)
+  gtest_discover_tests(hpp_unique)
+  gtest_discover_tests(hpp_view)
 
   if(NANOARROW_IPC)
 
diff --git a/ci/scripts/bundle.py b/ci/scripts/bundle.py
index 542d9ea7..746fcd37 100644
--- a/ci/scripts/bundle.py
+++ b/ci/scripts/bundle.py
@@ -125,13 +125,22 @@ def bundle_nanoarrow(
     nanoarrow_h = re.sub(r'#include "(nanoarrow/)?[a-z_./]+"', "", nanoarrow_h)
     yield f"{output_include_dir}/nanoarrow.h", nanoarrow_h
 
-    # Generate files that don't need special handling
-    for filename in [
-        "nanoarrow.hpp",
-    ]:
-        content = read_content(src_dir / filename)
-        content = namespace_nanoarrow_includes(content, header_namespace)
-        yield f"{output_include_dir}/{filename}", content
+    # Generate nanoarrow/nanoarrow.hpp
+    nanoarrow_hpp = concatenate_content(
+        [
+            src_dir / "nanoarrow.hpp",
+            src_dir / "hpp" / "exception.hpp",
+            src_dir / "hpp" / "operators.hpp",
+            src_dir / "hpp" / "unique.hpp",
+            src_dir / "hpp" / "array_stream.hpp",
+            src_dir / "hpp" / "buffer.hpp",
+            src_dir / "hpp" / "view.hpp",
+        ]
+    )
+
+    nanoarrow_hpp = re.sub(r'#include "(nanoarrow/)?hpp/[a-z_./]+"', "", 
nanoarrow_hpp)
+    nanoarrow_hpp = namespace_nanoarrow_includes(nanoarrow_hpp, 
header_namespace)
+    yield f"{output_include_dir}/nanoarrow.hpp", nanoarrow_hpp
 
     # Generate nanoarrow/nanoarrow.c
     nanoarrow_c = concatenate_content(
diff --git a/meson.build b/meson.build
index 144319c7..3a9aa8a2 100644
--- a/meson.build
+++ b/meson.build
@@ -62,6 +62,16 @@ install_headers(
     subdir: 'nanoarrow',
 )
 
+install_headers(
+    'src/nanoarrow/hpp/array_stream.hpp',
+    'src/nanoarrow/hpp/buffer.hpp',
+    'src/nanoarrow/hpp/exception.hpp',
+    'src/nanoarrow/hpp/operators.hpp',
+    'src/nanoarrow/hpp/unique.hpp',
+    'src/nanoarrow/hpp/view.hpp',
+    subdir: 'nanoarrow/hpp',
+)
+
 install_headers(
     'src/nanoarrow/common/inline_array.h',
     'src/nanoarrow/common/inline_buffer.h',
@@ -181,7 +191,7 @@ if get_option('tests')
   gtest_dep = dependency('gtest_main')
   gmock_dep = dependency('gmock')
 
-  nanoarrow_tests = ['utils', 'buffer', 'array', 'schema', 'array-stream', 
'nanoarrow-hpp']
+  nanoarrow_tests = ['utils', 'buffer', 'array', 'schema', 'array-stream']
 
   foreach name : nanoarrow_tests
     exc = executable(
@@ -193,6 +203,18 @@ if get_option('tests')
     test(name, exc)
   endforeach
 
+  nanoarrow_hpp_tests = ['array_stream', 'buffer', 'exception', 'unique', 
'view']
+
+  foreach name : nanoarrow_hpp_tests
+    exc = executable(
+        'hpp-' + name + '-test',
+        sources: 'src/nanoarrow/hpp/' + name.replace('-', '_') + '_test.cc',
+        include_directories: incdir,
+        dependencies: [nanoarrow_testing_dep, gtest_dep, gmock_dep],
+    )
+    test(name, exc)
+  endforeach
+
   testing_test = executable('nanoarrow-testing-test',
                             'src/nanoarrow/testing/testing_test.cc',
                             include_directories: incdir,
@@ -210,7 +232,7 @@ if get_option('tests')
       ipc_test_files = {
           'ipc-decoder': {
               'src': 'decoder',
-              'deps': [nanoarrow_ipc_dep, arrow_dep, gtest_dep, gmock_dep],
+              'deps': [nanoarrow_ipc_dep, flatcc_dep, arrow_dep, gtest_dep, 
gmock_dep],
               'timeout': 30,
           },
           'ipc-reader': {
@@ -226,6 +248,7 @@ if get_option('tests')
               'deps': [
                   nanoarrow_testing_dep,
                   nanoarrow_ipc_dep,
+                  flatcc_dep,
                   zlib_dep,
                   arrow_dep,
                   gtest_dep,
diff --git a/src/nanoarrow/common/nanoarrow_hpp_test.cc 
b/src/nanoarrow/common/nanoarrow_hpp_test.cc
deleted file mode 100644
index b76aa4cf..00000000
--- a/src/nanoarrow/common/nanoarrow_hpp_test.cc
+++ /dev/null
@@ -1,415 +0,0 @@
-// Licensed to the Apache Software Foundation (ASF) under one
-// or more contributor license agreements.  See the NOTICE file
-// distributed with this work for additional information
-// regarding copyright ownership.  The ASF licenses this file
-// to you under the Apache License, Version 2.0 (the
-// "License"); you may not use this file except in compliance
-// with the License.  You may obtain a copy of the License at
-//
-//   http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing,
-// software distributed under the License is distributed on an
-// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-// KIND, either express or implied.  See the License for the
-// specific language governing permissions and limitations
-// under the License.
-
-#include <array>
-#include <cerrno>
-
-#include <gmock/gmock-matchers.h>
-#include <gtest/gtest.h>
-
-#include "nanoarrow/nanoarrow_gtest_util.hpp"
-#include "nanoarrow/nanoarrow_testing.hpp"
-
-using testing::ElementsAre;
-
-TEST(NanoarrowHppTest, NanoarrowHppExceptionTest) {
-  ASSERT_THROW(NANOARROW_THROW_NOT_OK(EINVAL), nanoarrow::Exception);
-  ASSERT_NO_THROW(NANOARROW_THROW_NOT_OK(NANOARROW_OK));
-  try {
-    NANOARROW_THROW_NOT_OK(EINVAL);
-  } catch (const nanoarrow::Exception& e) {
-    EXPECT_EQ(std::string(e.what()).substr(0, 24), "EINVAL failed with errno");
-  }
-}
-
-TEST(NanoarrowHppTest, NanoarrowHppUniqueArrayTest) {
-  nanoarrow::UniqueArray array;
-  EXPECT_EQ(array->release, nullptr);
-
-  ASSERT_EQ(ArrowArrayInitFromType(array.get(), NANOARROW_TYPE_INT32), 
NANOARROW_OK);
-  ASSERT_EQ(ArrowArrayStartAppending(array.get()), NANOARROW_OK);
-  ASSERT_EQ(ArrowArrayAppendInt(array.get(), 123), NANOARROW_OK);
-  ASSERT_EQ(ArrowArrayFinishBuildingDefault(array.get(), nullptr), 
NANOARROW_OK);
-
-  EXPECT_NE(array->release, nullptr);
-  EXPECT_EQ(array->length, 1);
-
-  // move constructor
-  nanoarrow::UniqueArray array2 = std::move(array);
-  EXPECT_EQ(array->release, nullptr);  // NOLINT(clang-analyzer-cplusplus.Move)
-  EXPECT_NE(array2->release, nullptr);
-  EXPECT_EQ(array2->length, 1);
-
-  // pointer constructor
-  nanoarrow::UniqueArray array3(array2.get());
-  EXPECT_EQ(array2->release, nullptr);
-  EXPECT_NE(array3->release, nullptr);
-  EXPECT_EQ(array3->length, 1);
-}
-
-TEST(NanoarrowHppTest, NanoarrowHppUniqueSchemaTest) {
-  nanoarrow::UniqueSchema schema;
-  EXPECT_EQ(schema->release, nullptr);
-
-  ASSERT_EQ(ArrowSchemaInitFromType(schema.get(), NANOARROW_TYPE_INT32), 
NANOARROW_OK);
-  EXPECT_NE(schema->release, nullptr);
-  EXPECT_STREQ(schema->format, "i");
-
-  // move constructor
-  nanoarrow::UniqueSchema schema2 = std::move(schema);
-  EXPECT_EQ(schema->release, nullptr);  // 
NOLINT(clang-analyzer-cplusplus.Move)
-  EXPECT_NE(schema2->release, nullptr);
-  EXPECT_STREQ(schema2->format, "i");
-
-  // pointer constructor
-  nanoarrow::UniqueSchema schema3(schema2.get());
-  EXPECT_EQ(schema2->release, nullptr);
-  EXPECT_NE(schema3->release, nullptr);
-  EXPECT_STREQ(schema3->format, "i");
-}
-
-TEST(NanoarrowHppTest, NanoarrowHppUniqueArrayStreamTest) {
-  nanoarrow::UniqueSchema schema;
-  schema->format = NULL;
-
-  nanoarrow::UniqueArrayStream array_stream_default;
-  EXPECT_EQ(array_stream_default->release, nullptr);
-
-  nanoarrow::UniqueSchema schema_in;
-  EXPECT_EQ(ArrowSchemaInitFromType(schema_in.get(), NANOARROW_TYPE_INT32), 
NANOARROW_OK);
-  auto array_stream = nanoarrow::EmptyArrayStream::MakeUnique(schema_in.get());
-  EXPECT_NE(array_stream->release, nullptr);
-  EXPECT_EQ(ArrowArrayStreamGetSchema(array_stream.get(), schema.get(), 
nullptr),
-            NANOARROW_OK);
-  EXPECT_STREQ(schema->format, "i");
-  schema.reset();
-  schema->format = NULL;
-
-  // move constructor
-  nanoarrow::UniqueArrayStream array_stream2 = std::move(array_stream);
-  EXPECT_EQ(array_stream->release, nullptr);  // 
NOLINT(clang-analyzer-cplusplus.Move)
-  EXPECT_NE(array_stream2->release, nullptr);
-  EXPECT_EQ(ArrowArrayStreamGetSchema(array_stream2.get(), schema.get(), 
nullptr),
-            NANOARROW_OK);
-  EXPECT_STREQ(schema->format, "i");
-  schema.reset();
-  schema->format = NULL;
-
-  // pointer constructor
-  nanoarrow::UniqueArrayStream array_stream3(array_stream2.get());
-  EXPECT_EQ(array_stream2->release, nullptr);
-  EXPECT_NE(array_stream3->release, nullptr);
-  EXPECT_EQ(ArrowArrayStreamGetSchema(array_stream2.get(), schema.get(), 
nullptr),
-            NANOARROW_OK);
-  EXPECT_STREQ(schema->format, "i");
-
-  // releasing should clear the release callback
-  EXPECT_EQ(ArrowSchemaInitFromType(schema_in.get(), NANOARROW_TYPE_INT32), 
NANOARROW_OK);
-  auto array_stream4 = 
nanoarrow::EmptyArrayStream::MakeUnique(schema_in.get());
-  EXPECT_NE(array_stream4->release, nullptr);
-  array_stream4->release(array_stream4.get());
-  EXPECT_EQ(array_stream4->private_data, nullptr);
-  EXPECT_EQ(array_stream4->release, nullptr);
-}
-
-TEST(NanoarrowHppTest, NanoarrowHppUniqueBufferTest) {
-  nanoarrow::UniqueBuffer buffer;
-  EXPECT_EQ(buffer->data, nullptr);
-  EXPECT_EQ(buffer->size_bytes, 0);
-
-  ASSERT_EQ(ArrowBufferAppendFill(buffer.get(), 0xff, 123), NANOARROW_OK);
-  EXPECT_NE(buffer->data, nullptr);
-  EXPECT_EQ(buffer->size_bytes, 123);
-
-  // move constructor
-  nanoarrow::UniqueBuffer buffer2 = std::move(buffer);
-  EXPECT_EQ(buffer->data, nullptr);  // NOLINT(clang-analyzer-cplusplus.Move)
-  EXPECT_EQ(buffer->size_bytes, 0);  // NOLINT(clang-analyzer-cplusplus.Move)
-  EXPECT_NE(buffer2->data, nullptr);
-  EXPECT_EQ(buffer2->size_bytes, 123);
-
-  // pointer constructor
-  nanoarrow::UniqueBuffer buffer3(buffer2.get());
-  EXPECT_EQ(buffer2->data, nullptr);
-  EXPECT_EQ(buffer2->size_bytes, 0);
-  EXPECT_NE(buffer3->data, nullptr);
-  EXPECT_EQ(buffer3->size_bytes, 123);
-}
-
-TEST(NanoarrowHppTest, NanoarrowHppUniqueBitmapTest) {
-  nanoarrow::UniqueBitmap bitmap;
-  EXPECT_EQ(bitmap->buffer.data, nullptr);
-  EXPECT_EQ(bitmap->size_bits, 0);
-
-  ASSERT_EQ(ArrowBitmapAppend(bitmap.get(), true, 123), NANOARROW_OK);
-  EXPECT_NE(bitmap->buffer.data, nullptr);
-  EXPECT_EQ(bitmap->size_bits, 123);
-
-  // move constructor
-  nanoarrow::UniqueBitmap bitmap2 = std::move(bitmap);
-  EXPECT_EQ(bitmap->buffer.data, nullptr);  // 
NOLINT(clang-analyzer-cplusplus.Move)
-  EXPECT_EQ(bitmap->size_bits, 0);          // 
NOLINT(clang-analyzer-cplusplus.Move)
-  EXPECT_NE(bitmap2->buffer.data, nullptr);
-  EXPECT_EQ(bitmap2->size_bits, 123);
-
-  // pointer constructor
-  nanoarrow::UniqueBitmap bitmap3(bitmap2.get());
-  EXPECT_EQ(bitmap2->buffer.data, nullptr);
-  EXPECT_EQ(bitmap2->size_bits, 0);
-  EXPECT_NE(bitmap3->buffer.data, nullptr);
-  EXPECT_EQ(bitmap3->size_bits, 123);
-}
-
-struct TestWrappedObj {
-  int64_t* num_frees;
-
-  TestWrappedObj(int64_t* addr) { num_frees = addr; }
-
-  TestWrappedObj(TestWrappedObj&& obj) {
-    num_frees = obj.num_frees;
-    obj.num_frees = nullptr;
-  }
-
-  ~TestWrappedObj() {
-    if (num_frees != nullptr) {
-      *num_frees = *num_frees + 1;
-    }
-  }
-};
-
-TEST(NanoarrowHppTest, NanoarrowHppBufferInitWrappedTest) {
-  nanoarrow::UniqueBuffer buffer;
-  int64_t num_frees = 0;
-
-  TestWrappedObj obj(&num_frees);
-  nanoarrow::BufferInitWrapped(buffer.get(), std::move(obj), nullptr, 0);
-  EXPECT_EQ(obj.num_frees, nullptr);
-  EXPECT_EQ(num_frees, 0);
-  buffer.reset();
-  EXPECT_EQ(num_frees, 1);
-
-  // Ensure the destructor won't get called again when ArrowBufferReset is
-  // called on the empty buffer.
-  buffer.reset();
-  EXPECT_EQ(num_frees, 1);
-}
-
-TEST(NanoarrowHppTest, NanoarrowHppBufferInitSequenceTest) {
-  nanoarrow::UniqueBuffer buffer;
-
-  // Check templating magic with std::string
-  nanoarrow::BufferInitSequence(buffer.get(), std::string("1234"));
-  EXPECT_EQ(buffer->size_bytes, 4);
-  EXPECT_EQ(buffer->capacity_bytes, 0);
-  EXPECT_EQ(memcmp(buffer->data, "1234", 4), 0);
-
-  // Check templating magic with std::vector
-  buffer.reset();
-  nanoarrow::BufferInitSequence(buffer.get(), std::vector<uint8_t>({1, 2, 3, 
4}));
-  EXPECT_EQ(buffer->size_bytes, 4);
-  EXPECT_EQ(buffer->capacity_bytes, 0);
-  EXPECT_EQ(buffer->data[0], 1);
-  EXPECT_EQ(buffer->data[1], 2);
-  EXPECT_EQ(buffer->data[2], 3);
-  EXPECT_EQ(buffer->data[3], 4);
-
-  // Check templating magic with std::array
-  buffer.reset();
-  nanoarrow::BufferInitSequence(buffer.get(), std::array<uint8_t, 4>({1, 2, 3, 
4}));
-  EXPECT_EQ(buffer->size_bytes, 4);
-  EXPECT_EQ(buffer->capacity_bytes, 0);
-  EXPECT_EQ(buffer->data[0], 1);
-  EXPECT_EQ(buffer->data[1], 2);
-  EXPECT_EQ(buffer->data[2], 3);
-  EXPECT_EQ(buffer->data[3], 4);
-}
-
-TEST(NanoarrowHppTest, NanoarrowHppUniqueArrayViewTest) {
-  nanoarrow::UniqueArrayView array_view;
-  EXPECT_EQ(array_view->storage_type, NANOARROW_TYPE_UNINITIALIZED);
-
-  // Use an ArrayView with children, since an ArrayView with no children
-  // doesn't hold any resources
-  ArrowArrayViewInitFromType(array_view.get(), NANOARROW_TYPE_STRUCT);
-  ASSERT_EQ(ArrowArrayViewAllocateChildren(array_view.get(), 2), NANOARROW_OK);
-  EXPECT_EQ(array_view->storage_type, NANOARROW_TYPE_STRUCT);
-
-  // move constructor
-  nanoarrow::UniqueArrayView array_view2 = std::move(array_view);
-  EXPECT_EQ(array_view->storage_type,  // NOLINT(clang-analyzer-cplusplus.Move)
-            NANOARROW_TYPE_UNINITIALIZED);
-  EXPECT_EQ(array_view2->storage_type, NANOARROW_TYPE_STRUCT);
-
-  // pointer constructor
-  nanoarrow::UniqueArrayView array_view3(array_view2.get());
-  EXPECT_EQ(array_view2->storage_type, NANOARROW_TYPE_UNINITIALIZED);
-  EXPECT_EQ(array_view3->storage_type, NANOARROW_TYPE_STRUCT);
-}
-
-TEST(NanoarrowHppTest, NanoarrowHppViewArrayAsTest) {
-  nanoarrow::UniqueBuffer is_valid, floats;
-  nanoarrow::BufferInitSequence(is_valid.get(), std::vector<uint8_t>{0xFF});
-  ArrowBitClear(is_valid->data, 2);
-  ArrowBitClear(is_valid->data, 5);
-  nanoarrow::BufferInitSequence(floats.get(),
-                                std::vector<float>{8, 4, 2, 1, .5, .25, .125});
-
-  const void* buffers[] = {is_valid->data, floats->data};
-  struct ArrowArray array {};
-  array.length = 7;
-  array.null_count = 2;
-  array.n_buffers = 2;
-  array.buffers = buffers;
-
-  int i = 0;
-  float f = 8;
-  for (auto slot : nanoarrow::ViewArrayAs<float>(&array)) {
-    if (i == 2 || i == 5) {
-      EXPECT_EQ(slot, nanoarrow::NA);
-    } else {
-      EXPECT_EQ(slot, f);
-    }
-    ++i;
-    f /= 2;
-  }
-}
-
-TEST(NanoarrowHppTest, NanoarrowHppViewArrayAsBytesTest) {
-  using namespace nanoarrow::literals;
-
-  nanoarrow::UniqueBuffer is_valid, offsets, data;
-  nanoarrow::BufferInitSequence(is_valid.get(), std::vector<uint8_t>{0xFF});
-  ArrowBitClear(is_valid->data, 2);
-  ArrowBitClear(is_valid->data, 5);
-  nanoarrow::BufferInitSequence(offsets.get(),
-                                std::vector<int32_t>{0, 1, 2, 3, 4, 5, 6, 7});
-  nanoarrow::BufferInitSequence(data.get(), std::string{"abcdefghi"});
-
-  const void* buffers[] = {is_valid->data, offsets->data, data->data};
-  struct ArrowArray array {};
-  array.length = 7;
-  array.null_count = 2;
-  array.n_buffers = 2;
-  array.buffers = buffers;
-
-  int i = 0;
-  ArrowStringView expected[] = {"a"_asv, "b"_asv, "c"_asv, "d"_asv,
-                                "e"_asv, "f"_asv, "g"_asv};
-  for (auto slot : nanoarrow::ViewArrayAsBytes<32>(&array)) {
-    if (i == 2 || i == 5) {
-      EXPECT_EQ(slot, nanoarrow::NA);
-    } else {
-      EXPECT_EQ(slot, expected[i]);
-    }
-    ++i;
-  }
-}
-
-TEST(NanoarrowHppTest, NanoarrowHppViewArrayAsFixedSizeBytesTest) {
-  using namespace nanoarrow::literals;
-
-  nanoarrow::UniqueBuffer is_valid, data;
-  nanoarrow::BufferInitSequence(is_valid.get(), std::vector<uint8_t>{0xFF});
-  ArrowBitClear(is_valid->data, 2);
-  ArrowBitClear(is_valid->data, 5);
-  nanoarrow::BufferInitSequence(
-      data.get(), std::string{"foo"} + "bar" + "foo" + "bar" + "foo" + "bar" + 
"foo");
-
-  const void* buffers[] = {is_valid->data, data->data};
-  struct ArrowArray array {};
-  array.length = 7;
-  array.null_count = 2;
-  array.n_buffers = 2;
-  array.buffers = buffers;
-
-  int i = 0;
-  for (auto slot : nanoarrow::ViewArrayAsFixedSizeBytes(&array, 3)) {
-    if (i == 2 || i == 5) {
-      EXPECT_EQ(slot, nanoarrow::NA);
-    } else {
-      EXPECT_EQ(slot, i % 2 == 0 ? "foo"_asv : "bar"_asv);
-    }
-    ++i;
-  }
-}
-
-TEST(NanoarrowHppTest, NanoarrowHppViewArrayStreamTest) {
-  static int32_t slot = 1;
-
-  struct ArrowArrayStream stream {};
-  stream.get_schema = [](struct ArrowArrayStream*, struct ArrowSchema* out) {
-    return ArrowSchemaInitFromType(out, NANOARROW_TYPE_INT32);
-  };
-  stream.get_next = [](struct ArrowArrayStream*, struct ArrowArray* out) {
-    if (slot >= 16) return ENOMEM;
-    NANOARROW_RETURN_NOT_OK(ArrowArrayInitFromType(out, NANOARROW_TYPE_INT32));
-    NANOARROW_RETURN_NOT_OK(ArrowArrayStartAppending(out));
-    NANOARROW_RETURN_NOT_OK(ArrowArrayAppendInt(out, slot *= 2));
-    return ArrowArrayFinishBuildingDefault(out, nullptr);
-  };
-  stream.get_last_error = [](struct ArrowArrayStream*) { return "foo bar"; };
-  stream.release = [](struct ArrowArrayStream*) {};
-
-  nanoarrow::ViewArrayStream stream_view(&stream);
-  for (ArrowArray& array : stream_view) {
-    EXPECT_THAT(nanoarrow::ViewArrayAs<int32_t>(&array), ElementsAre(slot));
-  }
-  EXPECT_EQ(stream_view.count(), 4);
-  EXPECT_EQ(stream_view.code(), ENOMEM);
-  EXPECT_STREQ(stream_view.error()->message, "foo bar");
-}
-
-TEST(NanoarrowHppTest, NanoarrowHppEmptyArrayStreamTest) {
-  nanoarrow::UniqueSchema schema;
-  struct ArrowArray array;
-
-  nanoarrow::UniqueSchema schema_in;
-  EXPECT_EQ(ArrowSchemaInitFromType(schema_in.get(), NANOARROW_TYPE_INT32), 
NANOARROW_OK);
-
-  nanoarrow::UniqueArrayStream array_stream;
-  
nanoarrow::EmptyArrayStream(schema_in.get()).ToArrayStream(array_stream.get());
-
-  EXPECT_EQ(ArrowArrayStreamGetSchema(array_stream.get(), schema.get(), 
nullptr),
-            NANOARROW_OK);
-  EXPECT_STREQ(schema->format, "i");
-  EXPECT_EQ(ArrowArrayStreamGetNext(array_stream.get(), &array, nullptr), 
NANOARROW_OK);
-  EXPECT_EQ(array.release, nullptr);
-  EXPECT_STREQ(ArrowArrayStreamGetLastError(array_stream.get()), "");
-}
-
-TEST(NanoarrowHppTest, NanoarrowHppVectorArrayStreamTest) {
-  nanoarrow::UniqueArray array_in;
-  EXPECT_EQ(ArrowArrayInitFromType(array_in.get(), NANOARROW_TYPE_INT32), 
NANOARROW_OK);
-  EXPECT_EQ(ArrowArrayStartAppending(array_in.get()), NANOARROW_OK);
-  EXPECT_EQ(ArrowArrayAppendInt(array_in.get(), 1234), NANOARROW_OK);
-  EXPECT_EQ(ArrowArrayFinishBuildingDefault(array_in.get(), nullptr), 
NANOARROW_OK);
-
-  nanoarrow::UniqueSchema schema_in;
-  EXPECT_EQ(ArrowSchemaInitFromType(schema_in.get(), NANOARROW_TYPE_INT32), 
NANOARROW_OK);
-
-  nanoarrow::UniqueArrayStream array_stream;
-  nanoarrow::VectorArrayStream(schema_in.get(), array_in.get())
-      .ToArrayStream(array_stream.get());
-
-  nanoarrow::ViewArrayStream array_stream_view(array_stream.get());
-  for (ArrowArray& array : array_stream_view) {
-    EXPECT_THAT(nanoarrow::ViewArrayAs<int32_t>(&array), ElementsAre(1234));
-  }
-  EXPECT_EQ(array_stream_view.count(), 1);
-  EXPECT_EQ(array_stream_view.code(), NANOARROW_OK);
-  EXPECT_STREQ(array_stream_view.error()->message, "");
-}
diff --git a/src/nanoarrow/hpp/array_stream.hpp 
b/src/nanoarrow/hpp/array_stream.hpp
new file mode 100644
index 00000000..eb398925
--- /dev/null
+++ b/src/nanoarrow/hpp/array_stream.hpp
@@ -0,0 +1,204 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+#ifndef NANOARROW_HPP_ARRAY_STREAM_HPP_INCLUDED
+#define NANOARROW_HPP_ARRAY_STREAM_HPP_INCLUDED
+
+#include <vector>
+
+#include "nanoarrow/hpp/unique.hpp"
+
+NANOARROW_CXX_NAMESPACE_BEGIN
+
+/// \defgroup nanoarrow_hpp-array-stream ArrayStream helpers
+///
+/// These classes provide simple ArrowArrayStream implementations that
+/// can be extended to help simplify the process of creating a valid
+/// ArrowArrayStream implementation or used as-is for testing.
+///
+/// @{
+
+/// @brief Export an ArrowArrayStream from a standard C++ class
+/// @tparam T A class with methods `int GetSchema(ArrowSchema*)`, `int
+/// GetNext(ArrowArray*)`, and `const char* GetLastError()`
+///
+/// This class allows a standard C++ class to be exported to a generic 
ArrowArrayStream
+/// consumer by mapping C callback invocations to method calls on an instance 
of the
+/// object whose lifecycle is owned by the ArrowArrayStream. See 
VectorArrayStream for
+/// minimal useful example of this pattern.
+///
+/// The methods must be accessible to the ArrayStreamFactory, either as public 
methods or
+/// by declaring ArrayStreamFactory<ImplClass> a friend. Implementors are 
encouraged (but
+/// not required) to implement a ToArrayStream(ArrowArrayStream*) that creates 
a new
+/// instance owned by the ArrowArrayStream and moves the relevant data to that 
instance.
+///
+/// An example implementation might be:
+///
+/// \code
+/// class StreamImpl {
+///  public:
+///   // Public methods (e.g., constructor) used from C++ to initialize 
relevant data
+///
+///   // Idiomatic exporter to move data + lifecycle responsibility to an 
instance
+///   // managed by the ArrowArrayStream callbacks
+///   void ToArrayStream(struct ArrowArrayStream* out) {
+///     ArrayStreamFactory<StreamImpl>::InitArrayStream(new StreamImpl(...), 
out);
+///   }
+///
+///  private:
+///   // Make relevant methods available to the ArrayStreamFactory
+///   friend class ArrayStreamFactory<StreamImpl>;
+///
+///   // Method implementations (called from C, not normally interacted with 
from C++)
+///   int GetSchema(struct ArrowSchema* schema) { return ENOTSUP; }
+///   int GetNext(struct ArrowArray* array) { return ENOTSUP; }
+///   const char* GetLastError() { nullptr; }
+/// };
+/// \endcode
+///
+/// An example usage might be:
+///
+/// \code
+/// // Call constructor and/or public methods to initialize relevant data
+/// StreamImpl impl;
+///
+/// // Export to ArrowArrayStream after data are finalized
+/// UniqueArrayStream stream;
+/// impl.ToArrayStream(stream.get());
+/// \endcode
+template <typename T>
+class ArrayStreamFactory {
+ public:
+  /// \brief Take ownership of instance and populate callbacks of out
+  static void InitArrayStream(T* instance, struct ArrowArrayStream* out) {
+    out->get_schema = &get_schema_wrapper;
+    out->get_next = &get_next_wrapper;
+    out->get_last_error = &get_last_error_wrapper;
+    out->release = &release_wrapper;
+    out->private_data = instance;
+  }
+
+ private:
+  static int get_schema_wrapper(struct ArrowArrayStream* stream,
+                                struct ArrowSchema* schema) {
+    return reinterpret_cast<T*>(stream->private_data)->GetSchema(schema);
+  }
+
+  static int get_next_wrapper(struct ArrowArrayStream* stream, struct 
ArrowArray* array) {
+    return reinterpret_cast<T*>(stream->private_data)->GetNext(array);
+  }
+
+  static const char* get_last_error_wrapper(struct ArrowArrayStream* stream) {
+    return reinterpret_cast<T*>(stream->private_data)->GetLastError();
+  }
+
+  static void release_wrapper(struct ArrowArrayStream* stream) {
+    delete reinterpret_cast<T*>(stream->private_data);
+    stream->release = nullptr;
+    stream->private_data = nullptr;
+  }
+};
+
+/// \brief An empty array stream
+///
+/// This class can be constructed from an struct ArrowSchema and implements a 
default
+/// get_next() method that always marks the output ArrowArray as released.
+class EmptyArrayStream {
+ public:
+  /// \brief Create an EmptyArrayStream from an ArrowSchema
+  ///
+  /// Takes ownership of schema.
+  EmptyArrayStream(struct ArrowSchema* schema) : schema_(schema) {
+    ArrowErrorInit(&error_);
+  }
+
+  /// \brief Export to ArrowArrayStream
+  void ToArrayStream(struct ArrowArrayStream* out) {
+    EmptyArrayStream* impl = new EmptyArrayStream(schema_.get());
+    ArrayStreamFactory<EmptyArrayStream>::InitArrayStream(impl, out);
+  }
+
+ private:
+  UniqueSchema schema_;
+  struct ArrowError error_;
+
+  friend class ArrayStreamFactory<EmptyArrayStream>;
+
+  int GetSchema(struct ArrowSchema* schema) {
+    return ArrowSchemaDeepCopy(schema_.get(), schema);
+  }
+
+  int GetNext(struct ArrowArray* array) {
+    array->release = nullptr;
+    return NANOARROW_OK;
+  }
+
+  const char* GetLastError() { return error_.message; }
+};
+
+/// \brief Implementation of an ArrowArrayStream backed by a vector of 
UniqueArray objects
+class VectorArrayStream {
+ public:
+  /// \brief Create a VectorArrayStream from an ArrowSchema + vector of 
UniqueArray
+  ///
+  /// Takes ownership of schema and moves arrays if possible.
+  VectorArrayStream(struct ArrowSchema* schema, std::vector<UniqueArray> 
arrays)
+      : offset_(0), schema_(schema), arrays_(std::move(arrays)) {}
+
+  /// \brief Create a one-shot VectorArrayStream from an ArrowSchema + 
ArrowArray
+  ///
+  /// Takes ownership of schema and array.
+  VectorArrayStream(struct ArrowSchema* schema, struct ArrowArray* array)
+      : offset_(0), schema_(schema) {
+    arrays_.emplace_back(array);
+  }
+
+  /// \brief Export to ArrowArrayStream
+  void ToArrayStream(struct ArrowArrayStream* out) {
+    VectorArrayStream* impl = new VectorArrayStream(schema_.get(), 
std::move(arrays_));
+    ArrayStreamFactory<VectorArrayStream>::InitArrayStream(impl, out);
+  }
+
+ private:
+  int64_t offset_;
+  UniqueSchema schema_;
+  std::vector<UniqueArray> arrays_;
+
+  friend class ArrayStreamFactory<VectorArrayStream>;
+
+  int GetSchema(struct ArrowSchema* schema) {
+    return ArrowSchemaDeepCopy(schema_.get(), schema);
+  }
+
+  int GetNext(struct ArrowArray* array) {
+    if (offset_ < static_cast<int64_t>(arrays_.size())) {
+      arrays_[offset_++].move(array);
+    } else {
+      array->release = nullptr;
+    }
+
+    return NANOARROW_OK;
+  }
+
+  const char* GetLastError() { return ""; }
+};
+
+/// @}
+
+NANOARROW_CXX_NAMESPACE_END
+
+#endif
diff --git a/src/nanoarrow/hpp/array_stream_test.cc 
b/src/nanoarrow/hpp/array_stream_test.cc
new file mode 100644
index 00000000..5e178827
--- /dev/null
+++ b/src/nanoarrow/hpp/array_stream_test.cc
@@ -0,0 +1,64 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+#include <gmock/gmock-matchers.h>
+#include <gtest/gtest.h>
+
+#include "nanoarrow/nanoarrow.hpp"
+
+using testing::ElementsAre;
+
+TEST(HppArrayStream, EmptyArrayStream) {
+  nanoarrow::UniqueSchema schema;
+  struct ArrowArray array;
+
+  nanoarrow::UniqueSchema schema_in;
+  EXPECT_EQ(ArrowSchemaInitFromType(schema_in.get(), NANOARROW_TYPE_INT32), 
NANOARROW_OK);
+
+  nanoarrow::UniqueArrayStream array_stream;
+  
nanoarrow::EmptyArrayStream(schema_in.get()).ToArrayStream(array_stream.get());
+
+  EXPECT_EQ(ArrowArrayStreamGetSchema(array_stream.get(), schema.get(), 
nullptr),
+            NANOARROW_OK);
+  EXPECT_STREQ(schema->format, "i");
+  EXPECT_EQ(ArrowArrayStreamGetNext(array_stream.get(), &array, nullptr), 
NANOARROW_OK);
+  EXPECT_EQ(array.release, nullptr);
+  EXPECT_STREQ(ArrowArrayStreamGetLastError(array_stream.get()), "");
+}
+
+TEST(HppArrayStream, VectorArrayStream) {
+  nanoarrow::UniqueArray array_in;
+  EXPECT_EQ(ArrowArrayInitFromType(array_in.get(), NANOARROW_TYPE_INT32), 
NANOARROW_OK);
+  EXPECT_EQ(ArrowArrayStartAppending(array_in.get()), NANOARROW_OK);
+  EXPECT_EQ(ArrowArrayAppendInt(array_in.get(), 1234), NANOARROW_OK);
+  EXPECT_EQ(ArrowArrayFinishBuildingDefault(array_in.get(), nullptr), 
NANOARROW_OK);
+
+  nanoarrow::UniqueSchema schema_in;
+  EXPECT_EQ(ArrowSchemaInitFromType(schema_in.get(), NANOARROW_TYPE_INT32), 
NANOARROW_OK);
+
+  nanoarrow::UniqueArrayStream array_stream;
+  nanoarrow::VectorArrayStream(schema_in.get(), array_in.get())
+      .ToArrayStream(array_stream.get());
+
+  nanoarrow::ViewArrayStream array_stream_view(array_stream.get());
+  for (ArrowArray& array : array_stream_view) {
+    EXPECT_THAT(nanoarrow::ViewArrayAs<int32_t>(&array), ElementsAre(1234));
+  }
+  EXPECT_EQ(array_stream_view.count(), 1);
+  EXPECT_EQ(array_stream_view.code(), NANOARROW_OK);
+  EXPECT_STREQ(array_stream_view.error()->message, "");
+}
diff --git a/src/nanoarrow/hpp/buffer.hpp b/src/nanoarrow/hpp/buffer.hpp
new file mode 100644
index 00000000..b42ec0d0
--- /dev/null
+++ b/src/nanoarrow/hpp/buffer.hpp
@@ -0,0 +1,85 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+#ifndef NANOARROW_HPP_BUFFER_HPP_INCLUDED
+#define NANOARROW_HPP_BUFFER_HPP_INCLUDED
+
+#include <stdint.h>
+#include <utility>
+#include "nanoarrow/nanoarrow.h"
+
+NANOARROW_CXX_NAMESPACE_BEGIN
+
+namespace internal {
+template <typename T>
+static inline void DeallocateWrappedBuffer(struct ArrowBufferAllocator* 
allocator,
+                                           uint8_t* ptr, int64_t size) {
+  NANOARROW_UNUSED(ptr);
+  NANOARROW_UNUSED(size);
+  auto obj = reinterpret_cast<T*>(allocator->private_data);
+  delete obj;
+}
+}  // namespace internal
+
+/// \defgroup nanoarrow_hpp-buffer Buffer helpers
+///
+/// Helpers to wrap buffer-like C++ objects as ArrowBuffer objects that can
+/// be used to build ArrowArray objects.
+///
+/// @{
+
+/// \brief Initialize a buffer wrapping an arbitrary C++ object
+///
+/// Initializes a buffer with a release callback that deletes the moved obj
+/// when ArrowBufferReset is called. This version is useful for wrapping
+/// an object whose .data() member is missing or unrelated to the buffer
+/// value that is destined for a the buffer of an ArrowArray. T must be 
movable.
+template <typename T>
+static inline void BufferInitWrapped(struct ArrowBuffer* buffer, T obj,
+                                     const uint8_t* data, int64_t size_bytes) {
+  T* obj_moved = new T(std::move(obj));
+  buffer->data = const_cast<uint8_t*>(data);
+  buffer->size_bytes = size_bytes;
+  buffer->capacity_bytes = 0;
+  buffer->allocator =
+      ArrowBufferDeallocator(&internal::DeallocateWrappedBuffer<T>, obj_moved);
+}
+
+/// \brief Initialize a buffer wrapping a C++ sequence
+///
+/// Specifically, this uses obj.data() to set the buffer address and
+/// obj.size() * sizeof(T::value_type) to set the buffer size. This works
+/// for STL containers like std::vector, std::array, and std::string.
+/// This function moves obj and ensures it is deleted when ArrowBufferReset
+/// is called.
+template <typename T>
+void BufferInitSequence(struct ArrowBuffer* buffer, T obj) {
+  // Move before calling .data() (matters sometimes).
+  T* obj_moved = new T(std::move(obj));
+  buffer->data =
+      const_cast<uint8_t*>(reinterpret_cast<const 
uint8_t*>(obj_moved->data()));
+  buffer->size_bytes = obj_moved->size() * sizeof(typename T::value_type);
+  buffer->capacity_bytes = 0;
+  buffer->allocator =
+      ArrowBufferDeallocator(&internal::DeallocateWrappedBuffer<T>, obj_moved);
+}
+
+/// @}
+
+NANOARROW_CXX_NAMESPACE_END
+
+#endif
diff --git a/src/nanoarrow/hpp/buffer_test.cc b/src/nanoarrow/hpp/buffer_test.cc
new file mode 100644
index 00000000..19e5bedf
--- /dev/null
+++ b/src/nanoarrow/hpp/buffer_test.cc
@@ -0,0 +1,86 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+#include <array>
+
+#include <gtest/gtest.h>
+
+#include "nanoarrow/nanoarrow.hpp"
+
+struct TestWrappedObj {
+  int64_t* num_frees;
+
+  TestWrappedObj(int64_t* addr) { num_frees = addr; }
+
+  TestWrappedObj(TestWrappedObj&& obj) {
+    num_frees = obj.num_frees;
+    obj.num_frees = nullptr;
+  }
+
+  ~TestWrappedObj() {
+    if (num_frees != nullptr) {
+      *num_frees = *num_frees + 1;
+    }
+  }
+};
+
+TEST(HppBuffer, BufferInitWrapped) {
+  nanoarrow::UniqueBuffer buffer;
+  int64_t num_frees = 0;
+
+  TestWrappedObj obj(&num_frees);
+  nanoarrow::BufferInitWrapped(buffer.get(), std::move(obj), nullptr, 0);
+  EXPECT_EQ(obj.num_frees, nullptr);
+  EXPECT_EQ(num_frees, 0);
+  buffer.reset();
+  EXPECT_EQ(num_frees, 1);
+
+  // Ensure the destructor won't get called again when ArrowBufferReset is
+  // called on the empty buffer.
+  buffer.reset();
+  EXPECT_EQ(num_frees, 1);
+}
+
+TEST(HppBuffer, BufferInitSequence) {
+  nanoarrow::UniqueBuffer buffer;
+
+  // Check templating magic with std::string
+  nanoarrow::BufferInitSequence(buffer.get(), std::string("1234"));
+  EXPECT_EQ(buffer->size_bytes, 4);
+  EXPECT_EQ(buffer->capacity_bytes, 0);
+  EXPECT_EQ(memcmp(buffer->data, "1234", 4), 0);
+
+  // Check templating magic with std::vector
+  buffer.reset();
+  nanoarrow::BufferInitSequence(buffer.get(), std::vector<uint8_t>({1, 2, 3, 
4}));
+  EXPECT_EQ(buffer->size_bytes, 4);
+  EXPECT_EQ(buffer->capacity_bytes, 0);
+  EXPECT_EQ(buffer->data[0], 1);
+  EXPECT_EQ(buffer->data[1], 2);
+  EXPECT_EQ(buffer->data[2], 3);
+  EXPECT_EQ(buffer->data[3], 4);
+
+  // Check templating magic with std::array
+  buffer.reset();
+  nanoarrow::BufferInitSequence(buffer.get(), std::array<uint8_t, 4>({1, 2, 3, 
4}));
+  EXPECT_EQ(buffer->size_bytes, 4);
+  EXPECT_EQ(buffer->capacity_bytes, 0);
+  EXPECT_EQ(buffer->data[0], 1);
+  EXPECT_EQ(buffer->data[1], 2);
+  EXPECT_EQ(buffer->data[2], 3);
+  EXPECT_EQ(buffer->data[3], 4);
+}
diff --git a/src/nanoarrow/hpp/exception.hpp b/src/nanoarrow/hpp/exception.hpp
new file mode 100644
index 00000000..6ff4a01b
--- /dev/null
+++ b/src/nanoarrow/hpp/exception.hpp
@@ -0,0 +1,79 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+#ifndef NANOARROW_HPP_EXCEPTION_HPP_INCLUDED
+#define NANOARROW_HPP_EXCEPTION_HPP_INCLUDED
+
+#include <exception>
+#include <string>
+
+#include "nanoarrow/nanoarrow.h"
+
+NANOARROW_CXX_NAMESPACE_BEGIN
+
+/// \defgroup nanoarrow_hpp-errors Error handling helpers
+///
+/// Most functions in the C API return an ArrowErrorCode to communicate
+/// possible failure. Except where documented, it is usually not safe to
+/// continue after a non-zero value has been returned. While the
+/// nanoarrow C++ helpers do not throw any exceptions of their own,
+/// these helpers are provided to facilitate using the nanoarrow C++ helpers
+/// in frameworks where this is a useful error handling idiom.
+///
+/// @{
+
+class Exception : public std::exception {
+ public:
+  Exception(const std::string& msg) : msg_(msg) {}
+  const char* what() const noexcept { return msg_.c_str(); }
+
+ private:
+  std::string msg_;
+};
+
+#if defined(NANOARROW_DEBUG)
+#define _NANOARROW_THROW_NOT_OK_IMPL(NAME, EXPR, EXPR_STR)                     
 \
+  do {                                                                         
 \
+    const int NAME = (EXPR);                                                   
 \
+    if (NAME) {                                                                
 \
+      throw nanoarrow::Exception(                                              
 \
+          std::string(EXPR_STR) + std::string(" failed with errno ") +         
 \
+          std::to_string(NAME) + std::string("\n * ") + std::string(__FILE__) 
+ \
+          std::string(":") + std::to_string(__LINE__) + std::string("\n"));    
 \
+    }                                                                          
 \
+  } while (0)
+#else
+#define _NANOARROW_THROW_NOT_OK_IMPL(NAME, EXPR, EXPR_STR)            \
+  do {                                                                \
+    const int NAME = (EXPR);                                          \
+    if (NAME) {                                                       \
+      throw nanoarrow::Exception(std::string(EXPR_STR) +              \
+                                 std::string(" failed with errno ") + \
+                                 std::to_string(NAME));               \
+    }                                                                 \
+  } while (0)
+#endif
+
+#define NANOARROW_THROW_NOT_OK(EXPR)                                           
        \
+  _NANOARROW_THROW_NOT_OK_IMPL(_NANOARROW_MAKE_NAME(errno_status_, 
__COUNTER__), EXPR, \
+                               #EXPR)
+
+/// @}
+
+NANOARROW_CXX_NAMESPACE_END
+
+#endif
diff --git a/src/nanoarrow/nanoarrow_config.h.in 
b/src/nanoarrow/hpp/exception_test.cc
similarity index 61%
copy from src/nanoarrow/nanoarrow_config.h.in
copy to src/nanoarrow/hpp/exception_test.cc
index b29709a9..388afede 100644
--- a/src/nanoarrow/nanoarrow_config.h.in
+++ b/src/nanoarrow/hpp/exception_test.cc
@@ -15,18 +15,16 @@
 // specific language governing permissions and limitations
 // under the License.
 
-#ifndef NANOARROW_BUILD_ID_H_INCLUDED
-#define NANOARROW_BUILD_ID_H_INCLUDED
+#include <gtest/gtest.h>
 
-#define NANOARROW_VERSION_MAJOR @NANOARROW_VERSION_MAJOR@
-#define NANOARROW_VERSION_MINOR @NANOARROW_VERSION_MINOR@
-#define NANOARROW_VERSION_PATCH @NANOARROW_VERSION_PATCH@
-#define NANOARROW_VERSION "@NANOARROW_VERSION@"
+#include "nanoarrow/nanoarrow.hpp"
 
-#define NANOARROW_VERSION_INT                                        \
-  (NANOARROW_VERSION_MAJOR * 10000 + NANOARROW_VERSION_MINOR * 100 + \
-   NANOARROW_VERSION_PATCH)
-
-@NANOARROW_NAMESPACE_DEFINE@
-
-#endif
+TEST(HppException, Exception) {
+  ASSERT_THROW(NANOARROW_THROW_NOT_OK(EINVAL), nanoarrow::Exception);
+  ASSERT_NO_THROW(NANOARROW_THROW_NOT_OK(NANOARROW_OK));
+  try {
+    NANOARROW_THROW_NOT_OK(EINVAL);
+  } catch (const nanoarrow::Exception& e) {
+    EXPECT_EQ(std::string(e.what()).substr(0, 24), "EINVAL failed with errno");
+  }
+}
diff --git a/src/nanoarrow/hpp/operators.hpp b/src/nanoarrow/hpp/operators.hpp
new file mode 100644
index 00000000..23b854bf
--- /dev/null
+++ b/src/nanoarrow/hpp/operators.hpp
@@ -0,0 +1,62 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+#ifndef NANOARROW_HPP_OPERATORS_HPP_INCLUDED
+#define NANOARROW_HPP_OPERATORS_HPP_INCLUDED
+
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+
+#include "nanoarrow/nanoarrow.h"
+
+NANOARROW_CXX_NAMESPACE_BEGIN
+
+namespace literals {
+
+/// \defgroup nanoarrow_hpp-string_view_helpers ArrowStringView helpers
+///
+/// Factories and equality comparison for ArrowStringView.
+///
+/// @{
+
+/// \brief User literal operator allowing ArrowStringView construction like 
"str"_asv
+#if !defined(__clang__) && (defined(__GNUC__) && __GNUC__ < 6)
+inline ArrowStringView operator"" _asv(const char* data, size_t size_bytes) {
+  return {data, static_cast<int64_t>(size_bytes)};
+}
+#else
+inline ArrowStringView operator""_asv(const char* data, size_t size_bytes) {
+  return {data, static_cast<int64_t>(size_bytes)};
+}
+#endif
+// N.B. older GCC requires the space above, newer Clang forbids the space
+
+// @}
+
+}  // namespace literals
+
+NANOARROW_CXX_NAMESPACE_END
+
+/// \brief Equality comparison operator between ArrowStringView
+/// \ingroup nanoarrow_hpp-string_view_helpers
+inline bool operator==(ArrowStringView l, ArrowStringView r) {
+  if (l.size_bytes != r.size_bytes) return false;
+  return memcmp(l.data, r.data, l.size_bytes) == 0;
+}
+
+#endif
diff --git a/src/nanoarrow/hpp/unique.hpp b/src/nanoarrow/hpp/unique.hpp
new file mode 100644
index 00000000..a67f4fcd
--- /dev/null
+++ b/src/nanoarrow/hpp/unique.hpp
@@ -0,0 +1,226 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+#ifndef NANOARROW_HPP_UNIQUE_HPP_INCLUDED
+#define NANOARROW_HPP_UNIQUE_HPP_INCLUDED
+
+#include <string.h>
+
+#include "nanoarrow/nanoarrow.h"
+
+NANOARROW_CXX_NAMESPACE_BEGIN
+
+namespace internal {
+
+/// \defgroup nanoarrow_hpp-unique_base Base classes for Unique wrappers
+///
+/// @{
+
+template <typename T>
+static inline void init_pointer(T* data);
+
+template <typename T>
+static inline void move_pointer(T* src, T* dst);
+
+template <typename T>
+static inline void release_pointer(T* data);
+
+template <>
+inline void init_pointer(struct ArrowSchema* data) {
+  data->release = nullptr;
+}
+
+template <>
+inline void move_pointer(struct ArrowSchema* src, struct ArrowSchema* dst) {
+  ArrowSchemaMove(src, dst);
+}
+
+template <>
+inline void release_pointer(struct ArrowSchema* data) {
+  if (data->release != nullptr) {
+    data->release(data);
+  }
+}
+
+template <>
+inline void init_pointer(struct ArrowArray* data) {
+  data->release = nullptr;
+}
+
+template <>
+inline void move_pointer(struct ArrowArray* src, struct ArrowArray* dst) {
+  ArrowArrayMove(src, dst);
+}
+
+template <>
+inline void release_pointer(struct ArrowArray* data) {
+  if (data->release != nullptr) {
+    data->release(data);
+  }
+}
+
+template <>
+inline void init_pointer(struct ArrowArrayStream* data) {
+  data->release = nullptr;
+}
+
+template <>
+inline void move_pointer(struct ArrowArrayStream* src, struct 
ArrowArrayStream* dst) {
+  ArrowArrayStreamMove(src, dst);
+}
+
+template <>
+inline void release_pointer(ArrowArrayStream* data) {
+  if (data->release != nullptr) {
+    data->release(data);
+  }
+}
+
+template <>
+inline void init_pointer(struct ArrowBuffer* data) {
+  ArrowBufferInit(data);
+}
+
+template <>
+inline void move_pointer(struct ArrowBuffer* src, struct ArrowBuffer* dst) {
+  ArrowBufferMove(src, dst);
+}
+
+template <>
+inline void release_pointer(struct ArrowBuffer* data) {
+  ArrowBufferReset(data);
+}
+
+template <>
+inline void init_pointer(struct ArrowBitmap* data) {
+  ArrowBitmapInit(data);
+}
+
+template <>
+inline void move_pointer(struct ArrowBitmap* src, struct ArrowBitmap* dst) {
+  ArrowBitmapMove(src, dst);
+}
+
+template <>
+inline void release_pointer(struct ArrowBitmap* data) {
+  ArrowBitmapReset(data);
+}
+
+template <>
+inline void init_pointer(struct ArrowArrayView* data) {
+  ArrowArrayViewInitFromType(data, NANOARROW_TYPE_UNINITIALIZED);
+}
+
+template <>
+inline void move_pointer(struct ArrowArrayView* src, struct ArrowArrayView* 
dst) {
+  ArrowArrayViewMove(src, dst);
+}
+
+template <>
+inline void release_pointer(struct ArrowArrayView* data) {
+  ArrowArrayViewReset(data);
+}
+
+/// \brief A unique_ptr-like base class for stack-allocatable objects
+/// \tparam T The object type
+template <typename T>
+class Unique {
+ public:
+  /// \brief Construct an invalid instance of T holding no resources
+  Unique() {
+    memset(&data_, 0, sizeof(data_));
+    init_pointer(&data_);
+  }
+
+  /// \brief Move and take ownership of data
+  Unique(T* data) {
+    memset(&data_, 0, sizeof(data_));
+    move_pointer(data, &data_);
+  }
+
+  /// \brief Move and take ownership of data wrapped by rhs
+  Unique(Unique&& rhs) : Unique(rhs.get()) {}
+  Unique& operator=(Unique&& rhs) {
+    reset(rhs.get());
+    return *this;
+  }
+
+  // These objects are not copyable
+  Unique(const Unique& rhs) = delete;
+
+  /// \brief Get a pointer to the data owned by this object
+  T* get() noexcept { return &data_; }
+  const T* get() const noexcept { return &data_; }
+
+  /// \brief Use the pointer operator to access fields of this object
+  T* operator->() noexcept { return &data_; }
+  const T* operator->() const noexcept { return &data_; }
+
+  /// \brief Call data's release callback if valid
+  void reset() { release_pointer(&data_); }
+
+  /// \brief Call data's release callback if valid and move ownership of the 
data
+  /// pointed to by data
+  void reset(T* data) {
+    reset();
+    move_pointer(data, &data_);
+  }
+
+  /// \brief Move ownership of this object to the data pointed to by out
+  void move(T* out) { move_pointer(&data_, out); }
+
+  ~Unique() { reset(); }
+
+ protected:
+  T data_;
+};
+
+/// @}
+
+}  // namespace internal
+
+/// \defgroup nanoarrow_hpp-unique Unique object wrappers
+///
+/// The Arrow C Data interface, the Arrow C Stream interface, and the
+/// nanoarrow C library use stack-allocatable objects, some of which
+/// require initialization or cleanup.
+///
+/// @{
+
+/// \brief Class wrapping a unique struct ArrowSchema
+using UniqueSchema = internal::Unique<struct ArrowSchema>;
+
+/// \brief Class wrapping a unique struct ArrowArray
+using UniqueArray = internal::Unique<struct ArrowArray>;
+
+/// \brief Class wrapping a unique struct ArrowArrayStream
+using UniqueArrayStream = internal::Unique<struct ArrowArrayStream>;
+
+/// \brief Class wrapping a unique struct ArrowBuffer
+using UniqueBuffer = internal::Unique<struct ArrowBuffer>;
+
+/// \brief Class wrapping a unique struct ArrowBitmap
+using UniqueBitmap = internal::Unique<struct ArrowBitmap>;
+
+/// \brief Class wrapping a unique struct ArrowArrayView
+using UniqueArrayView = internal::Unique<struct ArrowArrayView>;
+
+/// @}
+
+NANOARROW_CXX_NAMESPACE_END
+
+#endif
diff --git a/src/nanoarrow/hpp/unique_test.cc b/src/nanoarrow/hpp/unique_test.cc
new file mode 100644
index 00000000..d9d25cac
--- /dev/null
+++ b/src/nanoarrow/hpp/unique_test.cc
@@ -0,0 +1,182 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+#include <gtest/gtest.h>
+
+#include "nanoarrow/nanoarrow.hpp"
+
+TEST(HppUnique, UniqueArray) {
+  nanoarrow::UniqueArray array;
+  EXPECT_EQ(array->release, nullptr);
+
+  ASSERT_EQ(ArrowArrayInitFromType(array.get(), NANOARROW_TYPE_INT32), 
NANOARROW_OK);
+  ASSERT_EQ(ArrowArrayStartAppending(array.get()), NANOARROW_OK);
+  ASSERT_EQ(ArrowArrayAppendInt(array.get(), 123), NANOARROW_OK);
+  ASSERT_EQ(ArrowArrayFinishBuildingDefault(array.get(), nullptr), 
NANOARROW_OK);
+
+  EXPECT_NE(array->release, nullptr);
+  EXPECT_EQ(array->length, 1);
+
+  // move constructor
+  nanoarrow::UniqueArray array2 = std::move(array);
+  EXPECT_EQ(array->release, nullptr);  // NOLINT(clang-analyzer-cplusplus.Move)
+  EXPECT_NE(array2->release, nullptr);
+  EXPECT_EQ(array2->length, 1);
+
+  // pointer constructor
+  nanoarrow::UniqueArray array3(array2.get());
+  EXPECT_EQ(array2->release, nullptr);
+  EXPECT_NE(array3->release, nullptr);
+  EXPECT_EQ(array3->length, 1);
+}
+
+TEST(HppUnique, UniqueSchema) {
+  nanoarrow::UniqueSchema schema;
+  EXPECT_EQ(schema->release, nullptr);
+
+  ASSERT_EQ(ArrowSchemaInitFromType(schema.get(), NANOARROW_TYPE_INT32), 
NANOARROW_OK);
+  EXPECT_NE(schema->release, nullptr);
+  EXPECT_STREQ(schema->format, "i");
+
+  // move constructor
+  nanoarrow::UniqueSchema schema2 = std::move(schema);
+  EXPECT_EQ(schema->release, nullptr);  // 
NOLINT(clang-analyzer-cplusplus.Move)
+  EXPECT_NE(schema2->release, nullptr);
+  EXPECT_STREQ(schema2->format, "i");
+
+  // pointer constructor
+  nanoarrow::UniqueSchema schema3(schema2.get());
+  EXPECT_EQ(schema2->release, nullptr);
+  EXPECT_NE(schema3->release, nullptr);
+  EXPECT_STREQ(schema3->format, "i");
+}
+
+TEST(HppUnique, UniqueArrayStream) {
+  nanoarrow::UniqueSchema schema;
+  schema->format = NULL;
+
+  nanoarrow::UniqueArrayStream array_stream_default;
+  EXPECT_EQ(array_stream_default->release, nullptr);
+
+  nanoarrow::UniqueSchema schema_in;
+  EXPECT_EQ(ArrowSchemaInitFromType(schema_in.get(), NANOARROW_TYPE_INT32), 
NANOARROW_OK);
+  nanoarrow::UniqueArrayStream array_stream;
+  
nanoarrow::EmptyArrayStream(schema_in.get()).ToArrayStream(array_stream.get());
+  EXPECT_NE(array_stream->release, nullptr);
+  EXPECT_EQ(ArrowArrayStreamGetSchema(array_stream.get(), schema.get(), 
nullptr),
+            NANOARROW_OK);
+  EXPECT_STREQ(schema->format, "i");
+  schema.reset();
+  schema->format = NULL;
+
+  // move constructor
+  nanoarrow::UniqueArrayStream array_stream2 = std::move(array_stream);
+  EXPECT_EQ(array_stream->release, nullptr);  // 
NOLINT(clang-analyzer-cplusplus.Move)
+  EXPECT_NE(array_stream2->release, nullptr);
+  EXPECT_EQ(ArrowArrayStreamGetSchema(array_stream2.get(), schema.get(), 
nullptr),
+            NANOARROW_OK);
+  EXPECT_STREQ(schema->format, "i");
+  schema.reset();
+  schema->format = NULL;
+
+  // pointer constructor
+  nanoarrow::UniqueArrayStream array_stream3(array_stream2.get());
+  EXPECT_EQ(array_stream2->release, nullptr);
+  EXPECT_NE(array_stream3->release, nullptr);
+  EXPECT_EQ(ArrowArrayStreamGetSchema(array_stream2.get(), schema.get(), 
nullptr),
+            NANOARROW_OK);
+  EXPECT_STREQ(schema->format, "i");
+
+  // releasing should clear the release callback
+  EXPECT_EQ(ArrowSchemaInitFromType(schema_in.get(), NANOARROW_TYPE_INT32), 
NANOARROW_OK);
+  nanoarrow::UniqueArrayStream array_stream4;
+  
nanoarrow::EmptyArrayStream(schema_in.get()).ToArrayStream(array_stream4.get());
+  EXPECT_NE(array_stream4->release, nullptr);
+  array_stream4->release(array_stream4.get());
+  EXPECT_EQ(array_stream4->private_data, nullptr);
+  EXPECT_EQ(array_stream4->release, nullptr);
+}
+
+TEST(HppUnique, UniqueBuffer) {
+  nanoarrow::UniqueBuffer buffer;
+  EXPECT_EQ(buffer->data, nullptr);
+  EXPECT_EQ(buffer->size_bytes, 0);
+
+  ASSERT_EQ(ArrowBufferAppendFill(buffer.get(), 0xff, 123), NANOARROW_OK);
+  EXPECT_NE(buffer->data, nullptr);
+  EXPECT_EQ(buffer->size_bytes, 123);
+
+  // move constructor
+  nanoarrow::UniqueBuffer buffer2 = std::move(buffer);
+  EXPECT_EQ(buffer->data, nullptr);  // NOLINT(clang-analyzer-cplusplus.Move)
+  EXPECT_EQ(buffer->size_bytes, 0);  // NOLINT(clang-analyzer-cplusplus.Move)
+  EXPECT_NE(buffer2->data, nullptr);
+  EXPECT_EQ(buffer2->size_bytes, 123);
+
+  // pointer constructor
+  nanoarrow::UniqueBuffer buffer3(buffer2.get());
+  EXPECT_EQ(buffer2->data, nullptr);
+  EXPECT_EQ(buffer2->size_bytes, 0);
+  EXPECT_NE(buffer3->data, nullptr);
+  EXPECT_EQ(buffer3->size_bytes, 123);
+}
+
+TEST(HppUnique, UniqueBitmap) {
+  nanoarrow::UniqueBitmap bitmap;
+  EXPECT_EQ(bitmap->buffer.data, nullptr);
+  EXPECT_EQ(bitmap->size_bits, 0);
+
+  ASSERT_EQ(ArrowBitmapAppend(bitmap.get(), true, 123), NANOARROW_OK);
+  EXPECT_NE(bitmap->buffer.data, nullptr);
+  EXPECT_EQ(bitmap->size_bits, 123);
+
+  // move constructor
+  nanoarrow::UniqueBitmap bitmap2 = std::move(bitmap);
+  EXPECT_EQ(bitmap->buffer.data, nullptr);  // 
NOLINT(clang-analyzer-cplusplus.Move)
+  EXPECT_EQ(bitmap->size_bits, 0);          // 
NOLINT(clang-analyzer-cplusplus.Move)
+  EXPECT_NE(bitmap2->buffer.data, nullptr);
+  EXPECT_EQ(bitmap2->size_bits, 123);
+
+  // pointer constructor
+  nanoarrow::UniqueBitmap bitmap3(bitmap2.get());
+  EXPECT_EQ(bitmap2->buffer.data, nullptr);
+  EXPECT_EQ(bitmap2->size_bits, 0);
+  EXPECT_NE(bitmap3->buffer.data, nullptr);
+  EXPECT_EQ(bitmap3->size_bits, 123);
+}
+
+TEST(HppUnique, UniqueArrayView) {
+  nanoarrow::UniqueArrayView array_view;
+  EXPECT_EQ(array_view->storage_type, NANOARROW_TYPE_UNINITIALIZED);
+
+  // Use an ArrayView with children, since an ArrayView with no children
+  // doesn't hold any resources
+  ArrowArrayViewInitFromType(array_view.get(), NANOARROW_TYPE_STRUCT);
+  ASSERT_EQ(ArrowArrayViewAllocateChildren(array_view.get(), 2), NANOARROW_OK);
+  EXPECT_EQ(array_view->storage_type, NANOARROW_TYPE_STRUCT);
+
+  // move constructor
+  nanoarrow::UniqueArrayView array_view2 = std::move(array_view);
+  EXPECT_EQ(array_view->storage_type,  // NOLINT(clang-analyzer-cplusplus.Move)
+            NANOARROW_TYPE_UNINITIALIZED);
+  EXPECT_EQ(array_view2->storage_type, NANOARROW_TYPE_STRUCT);
+
+  // pointer constructor
+  nanoarrow::UniqueArrayView array_view3(array_view2.get());
+  EXPECT_EQ(array_view2->storage_type, NANOARROW_TYPE_UNINITIALIZED);
+  EXPECT_EQ(array_view3->storage_type, NANOARROW_TYPE_STRUCT);
+}
diff --git a/src/nanoarrow/hpp/view.hpp b/src/nanoarrow/hpp/view.hpp
new file mode 100644
index 00000000..f0e267ff
--- /dev/null
+++ b/src/nanoarrow/hpp/view.hpp
@@ -0,0 +1,377 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+#ifndef NANOARROW_HPP_VIEW_HPP_INCLUDED
+#define NANOARROW_HPP_VIEW_HPP_INCLUDED
+
+#include <stdint.h>
+#include <type_traits>
+
+#include "nanoarrow/hpp/unique.hpp"
+#include "nanoarrow/nanoarrow.h"
+
+NANOARROW_CXX_NAMESPACE_BEGIN
+
+namespace internal {
+struct Nothing {};
+
+template <typename T>
+class Maybe {
+ public:
+  Maybe() : nothing_(Nothing()), is_something_(false) {}
+  Maybe(Nothing) : Maybe() {}
+
+  Maybe(T something)  // NOLINT(google-explicit-constructor)
+      : something_(something), is_something_(true) {}
+
+  explicit constexpr operator bool() const { return is_something_; }
+
+  const T& operator*() const { return something_; }
+
+  friend inline bool operator==(Maybe l, Maybe r) {
+    if (l.is_something_ != r.is_something_) return false;
+    return l.is_something_ ? l.something_ == r.something_ : true;
+  }
+  friend inline bool operator!=(Maybe l, Maybe r) { return !(l == r); }
+
+  T value_or(T val) const { return is_something_ ? something_ : val; }
+
+ private:
+  // When support for gcc 4.8 is dropped, we should also assert
+  // is_trivially_copyable<T>::value
+  static_assert(std::is_trivially_destructible<T>::value, "");
+
+  union {
+    Nothing nothing_;
+    T something_;
+  };
+  bool is_something_;
+};
+
+template <typename Get>
+struct RandomAccessRange {
+  Get get;
+  int64_t size;
+
+  using value_type = decltype(std::declval<Get>()(0));
+
+  struct const_iterator {
+    int64_t i;
+    const RandomAccessRange* range;
+    bool operator==(const_iterator other) const { return i == other.i; }
+    bool operator!=(const_iterator other) const { return i != other.i; }
+    const_iterator& operator++() { return ++i, *this; }
+    value_type operator*() const { return range->get(i); }
+  };
+
+  const_iterator begin() const { return {0, this}; }
+  const_iterator end() const { return {size, this}; }
+};
+
+template <typename Next>
+struct InputRange {
+  Next next;
+  using ValueOrFalsy = decltype(std::declval<Next>()());
+
+  static_assert(std::is_constructible<bool, ValueOrFalsy>::value, "");
+  static_assert(std::is_default_constructible<ValueOrFalsy>::value, "");
+  using value_type = decltype(*std::declval<ValueOrFalsy>());
+
+  struct iterator {
+    InputRange* range;
+    ValueOrFalsy stashed;
+
+    bool operator==(iterator other) const {
+      return static_cast<bool>(stashed) == static_cast<bool>(other.stashed);
+    }
+    bool operator!=(iterator other) const { return !(*this == other); }
+
+    iterator& operator++() {
+      stashed = range->next();
+      return *this;
+    }
+    value_type operator*() const { return *stashed; }
+  };
+
+  iterator begin() { return {this, next()}; }
+  iterator end() { return {this, ValueOrFalsy()}; }
+};
+}  // namespace internal
+
+/// \defgroup nanoarrow_hpp-range_for Range-for helpers
+///
+/// The Arrow C Data interface and the Arrow C Stream interface represent
+/// data which can be iterated through using C++'s range-for statement.
+///
+/// @{
+
+/// \brief An object convertible to any empty optional
+constexpr internal::Nothing NA{};
+
+/// \brief A range-for compatible wrapper for ArrowArray of fixed size type
+///
+/// Provides a sequence of optional<T> copied from each non-null
+/// slot of the wrapped array (null slots result in empty optionals).
+template <typename T>
+class ViewArrayAs {
+ private:
+  struct Get {
+    const uint8_t* validity;
+    const void* values;
+    int64_t offset;
+
+    internal::Maybe<T> operator()(int64_t i) const {
+      i += offset;
+      if (validity == nullptr || ArrowBitGet(validity, i)) {
+        if (std::is_same<T, bool>::value) {
+          return ArrowBitGet(static_cast<const uint8_t*>(values), i);
+        } else {
+          return static_cast<const T*>(values)[i];
+        }
+      }
+      return NA;
+    }
+  };
+
+  internal::RandomAccessRange<Get> range_;
+
+ public:
+  ViewArrayAs(const ArrowArrayView* array_view)
+      : range_{
+            Get{
+                array_view->buffer_views[0].data.as_uint8,
+                array_view->buffer_views[1].data.data,
+                array_view->offset,
+            },
+            array_view->length,
+        } {}
+
+  ViewArrayAs(const ArrowArray* array)
+      : range_{
+            Get{
+                static_cast<const uint8_t*>(array->buffers[0]),
+                array->buffers[1],
+                /*offset=*/0,
+            },
+            array->length,
+        } {}
+
+  using value_type = typename internal::RandomAccessRange<Get>::value_type;
+  using const_iterator = typename 
internal::RandomAccessRange<Get>::const_iterator;
+  const_iterator begin() const { return range_.begin(); }
+  const_iterator end() const { return range_.end(); }
+  value_type operator[](int64_t i) const { return range_.get(i); }
+};
+
+/// \brief A range-for compatible wrapper for ArrowArray of binary or utf8
+///
+/// Provides a sequence of optional<ArrowStringView> referencing each non-null
+/// slot of the wrapped array (null slots result in empty optionals). Large
+/// binary and utf8 arrays can be wrapped by specifying 64 instead of 32 for
+/// the template argument.
+template <int OffsetSize>
+class ViewArrayAsBytes {
+ private:
+  static_assert(OffsetSize == 32 || OffsetSize == 64, "");
+  using OffsetType = typename std::conditional<OffsetSize == 32, int32_t, 
int64_t>::type;
+
+  struct Get {
+    const uint8_t* validity;
+    const void* offsets;
+    const char* data;
+    int64_t offset;
+
+    internal::Maybe<ArrowStringView> operator()(int64_t i) const {
+      i += offset;
+      auto* offsets = static_cast<const OffsetType*>(this->offsets);
+      if (validity == nullptr || ArrowBitGet(validity, i)) {
+        return ArrowStringView{data + offsets[i], offsets[i + 1] - offsets[i]};
+      }
+      return NA;
+    }
+  };
+
+  internal::RandomAccessRange<Get> range_;
+
+ public:
+  ViewArrayAsBytes(const ArrowArrayView* array_view)
+      : range_{
+            Get{
+                array_view->buffer_views[0].data.as_uint8,
+                array_view->buffer_views[1].data.data,
+                array_view->buffer_views[2].data.as_char,
+                array_view->offset,
+            },
+            array_view->length,
+        } {}
+
+  ViewArrayAsBytes(const ArrowArray* array)
+      : range_{
+            Get{
+                static_cast<const uint8_t*>(array->buffers[0]),
+                array->buffers[1],
+                static_cast<const char*>(array->buffers[2]),
+                /*offset=*/0,
+            },
+            array->length,
+        } {}
+
+  using value_type = typename internal::RandomAccessRange<Get>::value_type;
+  using const_iterator = typename 
internal::RandomAccessRange<Get>::const_iterator;
+  const_iterator begin() const { return range_.begin(); }
+  const_iterator end() const { return range_.end(); }
+  value_type operator[](int64_t i) const { return range_.get(i); }
+};
+
+/// \brief A range-for compatible wrapper for ArrowArray of fixed size binary
+///
+/// Provides a sequence of optional<ArrowStringView> referencing each non-null
+/// slot of the wrapped array (null slots result in empty optionals).
+class ViewArrayAsFixedSizeBytes {
+ private:
+  struct Get {
+    const uint8_t* validity;
+    const char* data;
+    int64_t offset;
+    int fixed_size;
+
+    internal::Maybe<ArrowStringView> operator()(int64_t i) const {
+      i += offset;
+      if (validity == nullptr || ArrowBitGet(validity, i)) {
+        return ArrowStringView{data + i * fixed_size, fixed_size};
+      }
+      return NA;
+    }
+  };
+
+  internal::RandomAccessRange<Get> range_;
+
+ public:
+  ViewArrayAsFixedSizeBytes(const ArrowArrayView* array_view, int fixed_size)
+      : range_{
+            Get{
+                array_view->buffer_views[0].data.as_uint8,
+                array_view->buffer_views[1].data.as_char,
+                array_view->offset,
+                fixed_size,
+            },
+            array_view->length,
+        } {}
+
+  ViewArrayAsFixedSizeBytes(const ArrowArray* array, int fixed_size)
+      : range_{
+            Get{
+                static_cast<const uint8_t*>(array->buffers[0]),
+                static_cast<const char*>(array->buffers[1]),
+                /*offset=*/0,
+                fixed_size,
+            },
+            array->length,
+        } {}
+
+  using value_type = typename internal::RandomAccessRange<Get>::value_type;
+  using const_iterator = typename 
internal::RandomAccessRange<Get>::const_iterator;
+  const_iterator begin() const { return range_.begin(); }
+  const_iterator end() const { return range_.end(); }
+  value_type operator[](int64_t i) const { return range_.get(i); }
+};
+
+/// \brief A range-for compatible wrapper for ArrowArrayStream
+///
+/// Provides a sequence of ArrowArray& referencing the most recent array drawn
+/// from the wrapped stream. (Each array may be moved from if necessary.)
+/// When streams terminate due to an error, the error code and message are
+/// available for inspection through the code() and error() member functions
+/// respectively. Failure to inspect the error code will result in
+/// an assertion failure. The number of arrays drawn from the stream is also
+/// available through the count() member function.
+class ViewArrayStream {
+ public:
+  ViewArrayStream(ArrowArrayStream* stream, ArrowErrorCode* code, ArrowError* 
error)
+      : code_{code}, error_{error} {
+    // Using a slightly more verbose constructor to silence a warning that 
occurs
+    // on some versions of MSVC.
+    range_.next.self = this;
+    range_.next.stream = stream;
+  }
+
+  ViewArrayStream(ArrowArrayStream* stream, ArrowError* error)
+      : ViewArrayStream{stream, &internal_code_, error} {}
+
+  ViewArrayStream(ArrowArrayStream* stream)
+      : ViewArrayStream{stream, &internal_code_, &internal_error_} {}
+
+  // disable copy/move of this view, since its error references may point into 
itself
+  ViewArrayStream(ViewArrayStream&&) = delete;
+  ViewArrayStream& operator=(ViewArrayStream&&) = delete;
+  ViewArrayStream(const ViewArrayStream&) = delete;
+  ViewArrayStream& operator=(const ViewArrayStream&) = delete;
+
+  // ensure the error code of this stream was accessed at least once
+  ~ViewArrayStream() { NANOARROW_DCHECK(code_was_accessed_); }
+
+ private:
+  struct Next {
+    ViewArrayStream* self;
+    ArrowArrayStream* stream;
+    UniqueArray array;
+
+    ArrowArray* operator()() {
+      array.reset();
+      *self->code_ = ArrowArrayStreamGetNext(stream, array.get(), 
self->error_);
+
+      if (array->release != nullptr) {
+        NANOARROW_DCHECK(*self->code_ == NANOARROW_OK);
+        ++self->count_;
+        return array.get();
+      }
+
+      return nullptr;
+    }
+  };
+
+  internal::InputRange<Next> range_;
+  ArrowErrorCode* code_;
+  ArrowError* error_;
+  ArrowError internal_error_ = {};
+  ArrowErrorCode internal_code_;
+  bool code_was_accessed_ = false;
+  int count_ = 0;
+
+ public:
+  using value_type = typename internal::InputRange<Next>::value_type;
+  using iterator = typename internal::InputRange<Next>::iterator;
+  iterator begin() { return range_.begin(); }
+  iterator end() { return range_.end(); }
+
+  /// The error code which caused this stream to terminate, if any.
+  ArrowErrorCode code() {
+    code_was_accessed_ = true;
+    return *code_;
+  }
+  /// The error message which caused this stream to terminate, if any.
+  ArrowError* error() { return error_; }
+
+  /// The number of arrays streamed so far.
+  int count() const { return count_; }
+};
+
+/// @}
+
+NANOARROW_CXX_NAMESPACE_END
+
+#endif
diff --git a/src/nanoarrow/hpp/view_test.cc b/src/nanoarrow/hpp/view_test.cc
new file mode 100644
index 00000000..e18c7c63
--- /dev/null
+++ b/src/nanoarrow/hpp/view_test.cc
@@ -0,0 +1,137 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+#include <gmock/gmock-matchers.h>
+#include <gtest/gtest.h>
+
+#include "nanoarrow/nanoarrow.hpp"
+#include "nanoarrow/nanoarrow_gtest_util.hpp"
+
+using testing::ElementsAre;
+
+TEST(NanoarrowHppTest, NanoarrowHppViewArrayAsTest) {
+  nanoarrow::UniqueBuffer is_valid, floats;
+  nanoarrow::BufferInitSequence(is_valid.get(), std::vector<uint8_t>{0xFF});
+  ArrowBitClear(is_valid->data, 2);
+  ArrowBitClear(is_valid->data, 5);
+  nanoarrow::BufferInitSequence(floats.get(),
+                                std::vector<float>{8, 4, 2, 1, .5, .25, .125});
+
+  const void* buffers[] = {is_valid->data, floats->data};
+  struct ArrowArray array {};
+  array.length = 7;
+  array.null_count = 2;
+  array.n_buffers = 2;
+  array.buffers = buffers;
+
+  int i = 0;
+  float f = 8;
+  for (auto slot : nanoarrow::ViewArrayAs<float>(&array)) {
+    if (i == 2 || i == 5) {
+      EXPECT_EQ(slot, nanoarrow::NA);
+    } else {
+      EXPECT_EQ(slot, f);
+    }
+    ++i;
+    f /= 2;
+  }
+}
+
+TEST(NanoarrowHppTest, NanoarrowHppViewArrayAsBytesTest) {
+  using namespace nanoarrow::literals;
+
+  nanoarrow::UniqueBuffer is_valid, offsets, data;
+  nanoarrow::BufferInitSequence(is_valid.get(), std::vector<uint8_t>{0xFF});
+  ArrowBitClear(is_valid->data, 2);
+  ArrowBitClear(is_valid->data, 5);
+  nanoarrow::BufferInitSequence(offsets.get(),
+                                std::vector<int32_t>{0, 1, 2, 3, 4, 5, 6, 7});
+  nanoarrow::BufferInitSequence(data.get(), std::string{"abcdefghi"});
+
+  const void* buffers[] = {is_valid->data, offsets->data, data->data};
+  struct ArrowArray array {};
+  array.length = 7;
+  array.null_count = 2;
+  array.n_buffers = 2;
+  array.buffers = buffers;
+
+  int i = 0;
+  ArrowStringView expected[] = {"a"_asv, "b"_asv, "c"_asv, "d"_asv,
+                                "e"_asv, "f"_asv, "g"_asv};
+  for (auto slot : nanoarrow::ViewArrayAsBytes<32>(&array)) {
+    if (i == 2 || i == 5) {
+      EXPECT_EQ(slot, nanoarrow::NA);
+    } else {
+      EXPECT_EQ(slot, expected[i]);
+    }
+    ++i;
+  }
+}
+
+TEST(NanoarrowHppTest, NanoarrowHppViewArrayAsFixedSizeBytesTest) {
+  using namespace nanoarrow::literals;
+
+  nanoarrow::UniqueBuffer is_valid, data;
+  nanoarrow::BufferInitSequence(is_valid.get(), std::vector<uint8_t>{0xFF});
+  ArrowBitClear(is_valid->data, 2);
+  ArrowBitClear(is_valid->data, 5);
+  nanoarrow::BufferInitSequence(
+      data.get(), std::string{"foo"} + "bar" + "foo" + "bar" + "foo" + "bar" + 
"foo");
+
+  const void* buffers[] = {is_valid->data, data->data};
+  struct ArrowArray array {};
+  array.length = 7;
+  array.null_count = 2;
+  array.n_buffers = 2;
+  array.buffers = buffers;
+
+  int i = 0;
+  for (auto slot : nanoarrow::ViewArrayAsFixedSizeBytes(&array, 3)) {
+    if (i == 2 || i == 5) {
+      EXPECT_EQ(slot, nanoarrow::NA);
+    } else {
+      EXPECT_EQ(slot, i % 2 == 0 ? "foo"_asv : "bar"_asv);
+    }
+    ++i;
+  }
+}
+
+TEST(NanoarrowHppTest, NanoarrowHppViewArrayStreamTest) {
+  static int32_t slot = 1;
+
+  struct ArrowArrayStream stream {};
+  stream.get_schema = [](struct ArrowArrayStream*, struct ArrowSchema* out) {
+    return ArrowSchemaInitFromType(out, NANOARROW_TYPE_INT32);
+  };
+  stream.get_next = [](struct ArrowArrayStream*, struct ArrowArray* out) {
+    if (slot >= 16) return ENOMEM;
+    NANOARROW_RETURN_NOT_OK(ArrowArrayInitFromType(out, NANOARROW_TYPE_INT32));
+    NANOARROW_RETURN_NOT_OK(ArrowArrayStartAppending(out));
+    NANOARROW_RETURN_NOT_OK(ArrowArrayAppendInt(out, slot *= 2));
+    return ArrowArrayFinishBuildingDefault(out, nullptr);
+  };
+  stream.get_last_error = [](struct ArrowArrayStream*) { return "foo bar"; };
+  stream.release = [](struct ArrowArrayStream*) {};
+
+  nanoarrow::ViewArrayStream stream_view(&stream);
+  for (ArrowArray& array : stream_view) {
+    EXPECT_THAT(nanoarrow::ViewArrayAs<int32_t>(&array), ElementsAre(slot));
+  }
+  EXPECT_EQ(stream_view.count(), 4);
+  EXPECT_EQ(stream_view.code(), ENOMEM);
+  EXPECT_STREQ(stream_view.error()->message, "foo bar");
+}
diff --git a/src/nanoarrow/nanoarrow.hpp b/src/nanoarrow/nanoarrow.hpp
index 84125ada..855e1eab 100644
--- a/src/nanoarrow/nanoarrow.hpp
+++ b/src/nanoarrow/nanoarrow.hpp
@@ -15,16 +15,6 @@
 // specific language governing permissions and limitations
 // under the License.
 
-#ifndef NANOARROW_HPP_INCLUDED
-#define NANOARROW_HPP_INCLUDED
-
-#include <cstring>
-#include <exception>
-#include <string>
-#include <vector>
-
-#include "nanoarrow/nanoarrow.h"
-
 /// \defgroup nanoarrow_hpp Nanoarrow C++ Helpers
 ///
 /// The utilities provided in this file are intended to support C++ users
@@ -32,908 +22,9 @@
 /// and error handling can be used with nanoarrow data structures.
 /// These utilities are not intended to mirror the nanoarrow C API.
 
-namespace nanoarrow {
-
-/// \defgroup nanoarrow_hpp-errors Error handling helpers
-///
-/// Most functions in the C API return an ArrowErrorCode to communicate
-/// possible failure. Except where documented, it is usually not safe to
-/// continue after a non-zero value has been returned. While the
-/// nanoarrow C++ helpers do not throw any exceptions of their own,
-/// these helpers are provided to facilitate using the nanoarrow C++ helpers
-/// in frameworks where this is a useful error handling idiom.
-///
-/// @{
-
-class Exception : public std::exception {
- public:
-  Exception(const std::string& msg) : msg_(msg) {}
-  const char* what() const noexcept { return msg_.c_str(); }
-
- private:
-  std::string msg_;
-};
-
-#if defined(NANOARROW_DEBUG)
-#define _NANOARROW_THROW_NOT_OK_IMPL(NAME, EXPR, EXPR_STR)                     
 \
-  do {                                                                         
 \
-    const int NAME = (EXPR);                                                   
 \
-    if (NAME) {                                                                
 \
-      throw nanoarrow::Exception(                                              
 \
-          std::string(EXPR_STR) + std::string(" failed with errno ") +         
 \
-          std::to_string(NAME) + std::string("\n * ") + std::string(__FILE__) 
+ \
-          std::string(":") + std::to_string(__LINE__) + std::string("\n"));    
 \
-    }                                                                          
 \
-  } while (0)
-#else
-#define _NANOARROW_THROW_NOT_OK_IMPL(NAME, EXPR, EXPR_STR)            \
-  do {                                                                \
-    const int NAME = (EXPR);                                          \
-    if (NAME) {                                                       \
-      throw nanoarrow::Exception(std::string(EXPR_STR) +              \
-                                 std::string(" failed with errno ") + \
-                                 std::to_string(NAME));               \
-    }                                                                 \
-  } while (0)
-#endif
-
-#define NANOARROW_THROW_NOT_OK(EXPR)                                           
        \
-  _NANOARROW_THROW_NOT_OK_IMPL(_NANOARROW_MAKE_NAME(errno_status_, 
__COUNTER__), EXPR, \
-                               #EXPR)
-
-/// @}
-
-namespace literals {
-
-/// \defgroup nanoarrow_hpp-string_view_helpers ArrowStringView helpers
-///
-/// Factories and equality comparison for ArrowStringView.
-///
-/// @{
-
-/// \brief User literal operator allowing ArrowStringView construction like 
"str"_asv
-#if !defined(__clang__) && (defined(__GNUC__) && __GNUC__ < 6)
-inline ArrowStringView operator"" _asv(const char* data, std::size_t 
size_bytes) {
-  return {data, static_cast<int64_t>(size_bytes)};
-}
-#else
-inline ArrowStringView operator""_asv(const char* data, std::size_t 
size_bytes) {
-  return {data, static_cast<int64_t>(size_bytes)};
-}
-#endif
-// N.B. older GCC requires the space above, newer Clang forbids the space
-
-// @}
-
-}  // namespace literals
-
-namespace internal {
-
-/// \defgroup nanoarrow_hpp-unique_base Base classes for Unique wrappers
-///
-/// @{
-
-template <typename T>
-static inline void init_pointer(T* data);
-
-template <typename T>
-static inline void move_pointer(T* src, T* dst);
-
-template <typename T>
-static inline void release_pointer(T* data);
-
-template <>
-inline void init_pointer(struct ArrowSchema* data) {
-  data->release = nullptr;
-}
-
-template <>
-inline void move_pointer(struct ArrowSchema* src, struct ArrowSchema* dst) {
-  ArrowSchemaMove(src, dst);
-}
-
-template <>
-inline void release_pointer(struct ArrowSchema* data) {
-  if (data->release != nullptr) {
-    data->release(data);
-  }
-}
-
-template <>
-inline void init_pointer(struct ArrowArray* data) {
-  data->release = nullptr;
-}
-
-template <>
-inline void move_pointer(struct ArrowArray* src, struct ArrowArray* dst) {
-  ArrowArrayMove(src, dst);
-}
-
-template <>
-inline void release_pointer(struct ArrowArray* data) {
-  if (data->release != nullptr) {
-    data->release(data);
-  }
-}
-
-template <>
-inline void init_pointer(struct ArrowArrayStream* data) {
-  data->release = nullptr;
-}
-
-template <>
-inline void move_pointer(struct ArrowArrayStream* src, struct 
ArrowArrayStream* dst) {
-  ArrowArrayStreamMove(src, dst);
-}
-
-template <>
-inline void release_pointer(ArrowArrayStream* data) {
-  if (data->release != nullptr) {
-    data->release(data);
-  }
-}
-
-template <>
-inline void init_pointer(struct ArrowBuffer* data) {
-  ArrowBufferInit(data);
-}
-
-template <>
-inline void move_pointer(struct ArrowBuffer* src, struct ArrowBuffer* dst) {
-  ArrowBufferMove(src, dst);
-}
-
-template <>
-inline void release_pointer(struct ArrowBuffer* data) {
-  ArrowBufferReset(data);
-}
-
-template <>
-inline void init_pointer(struct ArrowBitmap* data) {
-  ArrowBitmapInit(data);
-}
-
-template <>
-inline void move_pointer(struct ArrowBitmap* src, struct ArrowBitmap* dst) {
-  ArrowBitmapMove(src, dst);
-}
-
-template <>
-inline void release_pointer(struct ArrowBitmap* data) {
-  ArrowBitmapReset(data);
-}
-
-template <>
-inline void init_pointer(struct ArrowArrayView* data) {
-  ArrowArrayViewInitFromType(data, NANOARROW_TYPE_UNINITIALIZED);
-}
-
-template <>
-inline void move_pointer(struct ArrowArrayView* src, struct ArrowArrayView* 
dst) {
-  ArrowArrayViewMove(src, dst);
-}
-
-template <>
-inline void release_pointer(struct ArrowArrayView* data) {
-  ArrowArrayViewReset(data);
-}
-
-/// \brief A unique_ptr-like base class for stack-allocatable objects
-/// \tparam T The object type
-template <typename T>
-class Unique {
- public:
-  /// \brief Construct an invalid instance of T holding no resources
-  Unique() {
-    std::memset(&data_, 0, sizeof(data_));
-    init_pointer(&data_);
-  }
-
-  /// \brief Move and take ownership of data
-  Unique(T* data) {
-    std::memset(&data_, 0, sizeof(data_));
-    move_pointer(data, &data_);
-  }
-
-  /// \brief Move and take ownership of data wrapped by rhs
-  Unique(Unique&& rhs) : Unique(rhs.get()) {}
-  Unique& operator=(Unique&& rhs) {
-    reset(rhs.get());
-    return *this;
-  }
-
-  // These objects are not copyable
-  Unique(const Unique& rhs) = delete;
-
-  /// \brief Get a pointer to the data owned by this object
-  T* get() noexcept { return &data_; }
-  const T* get() const noexcept { return &data_; }
-
-  /// \brief Use the pointer operator to access fields of this object
-  T* operator->() noexcept { return &data_; }
-  const T* operator->() const noexcept { return &data_; }
-
-  /// \brief Call data's release callback if valid
-  void reset() { release_pointer(&data_); }
-
-  /// \brief Call data's release callback if valid and move ownership of the 
data
-  /// pointed to by data
-  void reset(T* data) {
-    reset();
-    move_pointer(data, &data_);
-  }
-
-  /// \brief Move ownership of this object to the data pointed to by out
-  void move(T* out) { move_pointer(&data_, out); }
-
-  ~Unique() { reset(); }
-
- protected:
-  T data_;
-};
-
-template <typename T>
-static inline void DeallocateWrappedBuffer(struct ArrowBufferAllocator* 
allocator,
-                                           uint8_t* ptr, int64_t size) {
-  NANOARROW_UNUSED(ptr);
-  NANOARROW_UNUSED(size);
-  auto obj = reinterpret_cast<T*>(allocator->private_data);
-  delete obj;
-}
-
-/// @}
-
-}  // namespace internal
-
-/// \defgroup nanoarrow_hpp-unique Unique object wrappers
-///
-/// The Arrow C Data interface, the Arrow C Stream interface, and the
-/// nanoarrow C library use stack-allocatable objects, some of which
-/// require initialization or cleanup.
-///
-/// @{
-
-/// \brief Class wrapping a unique struct ArrowSchema
-using UniqueSchema = internal::Unique<struct ArrowSchema>;
-
-/// \brief Class wrapping a unique struct ArrowArray
-using UniqueArray = internal::Unique<struct ArrowArray>;
-
-/// \brief Class wrapping a unique struct ArrowArrayStream
-using UniqueArrayStream = internal::Unique<struct ArrowArrayStream>;
-
-/// \brief Class wrapping a unique struct ArrowBuffer
-using UniqueBuffer = internal::Unique<struct ArrowBuffer>;
-
-/// \brief Class wrapping a unique struct ArrowBitmap
-using UniqueBitmap = internal::Unique<struct ArrowBitmap>;
-
-/// \brief Class wrapping a unique struct ArrowArrayView
-using UniqueArrayView = internal::Unique<struct ArrowArrayView>;
-
-/// @}
-
-/// \defgroup nanoarrow_hpp-buffer Buffer helpers
-///
-/// Helpers to wrap buffer-like C++ objects as ArrowBuffer objects that can
-/// be used to build ArrowArray objects.
-///
-/// @{
-
-/// \brief Initialize a buffer wrapping an arbitrary C++ object
-///
-/// Initializes a buffer with a release callback that deletes the moved obj
-/// when ArrowBufferReset is called. This version is useful for wrapping
-/// an object whose .data() member is missing or unrelated to the buffer
-/// value that is destined for a the buffer of an ArrowArray. T must be 
movable.
-template <typename T>
-static inline void BufferInitWrapped(struct ArrowBuffer* buffer, T obj,
-                                     const uint8_t* data, int64_t size_bytes) {
-  T* obj_moved = new T(std::move(obj));
-  buffer->data = const_cast<uint8_t*>(data);
-  buffer->size_bytes = size_bytes;
-  buffer->capacity_bytes = 0;
-  buffer->allocator =
-      ArrowBufferDeallocator(&internal::DeallocateWrappedBuffer<T>, obj_moved);
-}
-
-/// \brief Initialize a buffer wrapping a C++ sequence
-///
-/// Specifically, this uses obj.data() to set the buffer address and
-/// obj.size() * sizeof(T::value_type) to set the buffer size. This works
-/// for STL containers like std::vector, std::array, and std::string.
-/// This function moves obj and ensures it is deleted when ArrowBufferReset
-/// is called.
-template <typename T>
-void BufferInitSequence(struct ArrowBuffer* buffer, T obj) {
-  // Move before calling .data() (matters sometimes).
-  T* obj_moved = new T(std::move(obj));
-  buffer->data =
-      const_cast<uint8_t*>(reinterpret_cast<const 
uint8_t*>(obj_moved->data()));
-  buffer->size_bytes = obj_moved->size() * sizeof(typename T::value_type);
-  buffer->capacity_bytes = 0;
-  buffer->allocator =
-      ArrowBufferDeallocator(&internal::DeallocateWrappedBuffer<T>, obj_moved);
-}
-
-/// @}
-
-/// \defgroup nanoarrow_hpp-array-stream ArrayStream helpers
-///
-/// These classes provide simple ArrowArrayStream implementations that
-/// can be extended to help simplify the process of creating a valid
-/// ArrowArrayStream implementation or used as-is for testing.
-///
-/// @{
-
-/// @brief Export an ArrowArrayStream from a standard C++ class
-/// @tparam T A class with methods `int GetSchema(ArrowSchema*)`, `int
-/// GetNext(ArrowArray*)`, and `const char* GetLastError()`
-///
-/// This class allows a standard C++ class to be exported to a generic 
ArrowArrayStream
-/// consumer by mapping C callback invocations to method calls on an instance 
of the
-/// object whose lifecycle is owned by the ArrowArrayStream. See 
VectorArrayStream for
-/// minimal useful example of this pattern.
-///
-/// The methods must be accessible to the ArrayStreamFactory, either as public 
methods or
-/// by declaring ArrayStreamFactory<ImplClass> a friend. Implementors are 
encouraged (but
-/// not required) to implement a ToArrayStream(ArrowArrayStream*) that creates 
a new
-/// instance owned by the ArrowArrayStream and moves the relevant data to that 
instance.
-///
-/// An example implementation might be:
-///
-/// \code
-/// class StreamImpl {
-///  public:
-///   // Public methods (e.g., constructor) used from C++ to initialize 
relevant data
-///
-///   // Idiomatic exporter to move data + lifecycle responsibility to an 
instance
-///   // managed by the ArrowArrayStream callbacks
-///   void ToArrayStream(struct ArrowArrayStream* out) {
-///     ArrayStreamFactory<StreamImpl>::InitArrayStream(new StreamImpl(...), 
out);
-///   }
-///
-///  private:
-///   // Make relevant methods available to the ArrayStreamFactory
-///   friend class ArrayStreamFactory<StreamImpl>;
-///
-///   // Method implementations (called from C, not normally interacted with 
from C++)
-///   int GetSchema(struct ArrowSchema* schema) { return ENOTSUP; }
-///   int GetNext(struct ArrowArray* array) { return ENOTSUP; }
-///   const char* GetLastError() { nullptr; }
-/// };
-/// \endcode
-///
-/// An example usage might be:
-///
-/// \code
-/// // Call constructor and/or public methods to initialize relevant data
-/// StreamImpl impl;
-///
-/// // Export to ArrowArrayStream after data are finalized
-/// UniqueArrayStream stream;
-/// impl.ToArrayStream(stream.get());
-/// \endcode
-template <typename T>
-class ArrayStreamFactory {
- public:
-  /// \brief Take ownership of instance and populate callbacks of out
-  static void InitArrayStream(T* instance, struct ArrowArrayStream* out) {
-    out->get_schema = &get_schema_wrapper;
-    out->get_next = &get_next_wrapper;
-    out->get_last_error = &get_last_error_wrapper;
-    out->release = &release_wrapper;
-    out->private_data = instance;
-  }
-
- private:
-  static int get_schema_wrapper(struct ArrowArrayStream* stream,
-                                struct ArrowSchema* schema) {
-    return reinterpret_cast<T*>(stream->private_data)->GetSchema(schema);
-  }
-
-  static int get_next_wrapper(struct ArrowArrayStream* stream, struct 
ArrowArray* array) {
-    return reinterpret_cast<T*>(stream->private_data)->GetNext(array);
-  }
-
-  static const char* get_last_error_wrapper(struct ArrowArrayStream* stream) {
-    return reinterpret_cast<T*>(stream->private_data)->GetLastError();
-  }
-
-  static void release_wrapper(struct ArrowArrayStream* stream) {
-    delete reinterpret_cast<T*>(stream->private_data);
-    stream->release = nullptr;
-    stream->private_data = nullptr;
-  }
-};
-
-/// \brief An empty array stream
-///
-/// This class can be constructed from an struct ArrowSchema and implements a 
default
-/// get_next() method that always marks the output ArrowArray as released.
-///
-/// DEPRECATED (0.4.0): Early versions of nanoarrow allowed subclasses to 
override
-/// get_schema(), get_next(), and get_last_error(). This functionality will be 
removed
-/// in a future release: use the pattern documented in ArrayStreamFactory to 
create
-/// custom ArrowArrayStream implementations.
-class EmptyArrayStream {
- public:
-  /// \brief Create an EmptyArrayStream from an ArrowSchema
-  ///
-  /// Takes ownership of schema.
-  EmptyArrayStream(struct ArrowSchema* schema) : schema_(schema) {
-    ArrowErrorInit(&error_);
-  }
-
-  /// \brief Export to ArrowArrayStream
-  void ToArrayStream(struct ArrowArrayStream* out) {
-    EmptyArrayStream* impl = new EmptyArrayStream(schema_.get());
-    ArrayStreamFactory<EmptyArrayStream>::InitArrayStream(impl, out);
-  }
-
-  /// \brief Create an empty UniqueArrayStream from a struct ArrowSchema
-  ///
-  /// DEPRECATED (0.4.0): Use the constructor + ToArrayStream() to export an
-  /// EmptyArrayStream to an ArrowArrayStream consumer.
-  static UniqueArrayStream MakeUnique(struct ArrowSchema* schema) {
-    UniqueArrayStream stream;
-    EmptyArrayStream(schema).ToArrayStream(stream.get());
-    return stream;
-  }
-
-  virtual ~EmptyArrayStream() {}
-
- protected:
-  UniqueSchema schema_;
-  struct ArrowError error_;
-
-  void MakeStream(struct ArrowArrayStream* stream) { ToArrayStream(stream); }
-
-  virtual int get_schema(struct ArrowSchema* schema) {
-    return ArrowSchemaDeepCopy(schema_.get(), schema);
-  }
-
-  virtual int get_next(struct ArrowArray* array) {
-    array->release = nullptr;
-    return NANOARROW_OK;
-  }
-
-  virtual const char* get_last_error() { return error_.message; }
-
- private:
-  friend class ArrayStreamFactory<EmptyArrayStream>;
-
-  int GetSchema(struct ArrowSchema* schema) { return get_schema(schema); }
-
-  int GetNext(struct ArrowArray* array) { return get_next(array); }
-
-  const char* GetLastError() { return get_last_error(); }
-};
-
-/// \brief Implementation of an ArrowArrayStream backed by a vector of 
UniqueArray objects
-class VectorArrayStream {
- public:
-  /// \brief Create a VectorArrayStream from an ArrowSchema + vector of 
UniqueArray
-  ///
-  /// Takes ownership of schema and moves arrays if possible.
-  VectorArrayStream(struct ArrowSchema* schema, std::vector<UniqueArray> 
arrays)
-      : offset_(0), schema_(schema), arrays_(std::move(arrays)) {}
-
-  /// \brief Create a one-shot VectorArrayStream from an ArrowSchema + 
ArrowArray
-  ///
-  /// Takes ownership of schema and array.
-  VectorArrayStream(struct ArrowSchema* schema, struct ArrowArray* array)
-      : offset_(0), schema_(schema) {
-    arrays_.emplace_back(array);
-  }
-
-  /// \brief Export to ArrowArrayStream
-  void ToArrayStream(struct ArrowArrayStream* out) {
-    VectorArrayStream* impl = new VectorArrayStream(schema_.get(), 
std::move(arrays_));
-    ArrayStreamFactory<VectorArrayStream>::InitArrayStream(impl, out);
-  }
-
-  /// \brief Create a UniqueArrowArrayStream from an existing array
-  ///
-  /// DEPRECATED (0.4.0): Use the constructors + ToArrayStream() to export a
-  /// VectorArrayStream to an ArrowArrayStream consumer.
-  static UniqueArrayStream MakeUnique(struct ArrowSchema* schema,
-                                      struct ArrowArray* array) {
-    UniqueArrayStream stream;
-    VectorArrayStream(schema, array).ToArrayStream(stream.get());
-    return stream;
-  }
-
-  /// \brief Create a UniqueArrowArrayStream from existing arrays
-  ///
-  /// DEPRECATED (0.4.0): Use the constructor + ToArrayStream() to export a
-  /// VectorArrayStream to an ArrowArrayStream consumer.
-  static UniqueArrayStream MakeUnique(struct ArrowSchema* schema,
-                                      std::vector<UniqueArray> arrays) {
-    UniqueArrayStream stream;
-    VectorArrayStream(schema, std::move(arrays)).ToArrayStream(stream.get());
-    return stream;
-  }
-
- private:
-  int64_t offset_;
-  UniqueSchema schema_;
-  std::vector<UniqueArray> arrays_;
-
-  friend class ArrayStreamFactory<VectorArrayStream>;
-
-  int GetSchema(struct ArrowSchema* schema) {
-    return ArrowSchemaDeepCopy(schema_.get(), schema);
-  }
-
-  int GetNext(struct ArrowArray* array) {
-    if (offset_ < static_cast<int64_t>(arrays_.size())) {
-      arrays_[offset_++].move(array);
-    } else {
-      array->release = nullptr;
-    }
-
-    return NANOARROW_OK;
-  }
-
-  const char* GetLastError() { return ""; }
-};
-
-/// @}
-
-namespace internal {
-struct Nothing {};
-
-template <typename T>
-class Maybe {
- public:
-  Maybe() : nothing_(Nothing()), is_something_(false) {}
-  Maybe(Nothing) : Maybe() {}
-
-  Maybe(T something)  // NOLINT(google-explicit-constructor)
-      : something_(something), is_something_(true) {}
-
-  explicit constexpr operator bool() const { return is_something_; }
-
-  const T& operator*() const { return something_; }
-
-  friend inline bool operator==(Maybe l, Maybe r) {
-    if (l.is_something_ != r.is_something_) return false;
-    return l.is_something_ ? l.something_ == r.something_ : true;
-  }
-  friend inline bool operator!=(Maybe l, Maybe r) { return !(l == r); }
-
-  T value_or(T val) const { return is_something_ ? something_ : val; }
-
- private:
-  // When support for gcc 4.8 is dropped, we should also assert
-  // is_trivially_copyable<T>::value
-  static_assert(std::is_trivially_destructible<T>::value, "");
-
-  union {
-    Nothing nothing_;
-    T something_;
-  };
-  bool is_something_;
-};
-
-template <typename Get>
-struct RandomAccessRange {
-  Get get;
-  int64_t size;
-
-  using value_type = decltype(std::declval<Get>()(0));
-
-  struct const_iterator {
-    int64_t i;
-    const RandomAccessRange* range;
-    bool operator==(const_iterator other) const { return i == other.i; }
-    bool operator!=(const_iterator other) const { return i != other.i; }
-    const_iterator& operator++() { return ++i, *this; }
-    value_type operator*() const { return range->get(i); }
-  };
-
-  const_iterator begin() const { return {0, this}; }
-  const_iterator end() const { return {size, this}; }
-};
-
-template <typename Next>
-struct InputRange {
-  Next next;
-  using ValueOrFalsy = decltype(std::declval<Next>()());
-
-  static_assert(std::is_constructible<bool, ValueOrFalsy>::value, "");
-  static_assert(std::is_default_constructible<ValueOrFalsy>::value, "");
-  using value_type = decltype(*std::declval<ValueOrFalsy>());
-
-  struct iterator {
-    InputRange* range;
-    ValueOrFalsy stashed;
-
-    bool operator==(iterator other) const {
-      return static_cast<bool>(stashed) == static_cast<bool>(other.stashed);
-    }
-    bool operator!=(iterator other) const { return !(*this == other); }
-
-    iterator& operator++() {
-      stashed = range->next();
-      return *this;
-    }
-    value_type operator*() const { return *stashed; }
-  };
-
-  iterator begin() { return {this, next()}; }
-  iterator end() { return {this, ValueOrFalsy()}; }
-};
-}  // namespace internal
-
-/// \defgroup nanoarrow_hpp-range_for Range-for helpers
-///
-/// The Arrow C Data interface and the Arrow C Stream interface represent
-/// data which can be iterated through using C++'s range-for statement.
-///
-/// @{
-
-/// \brief An object convertible to any empty optional
-constexpr internal::Nothing NA{};
-
-/// \brief A range-for compatible wrapper for ArrowArray of fixed size type
-///
-/// Provides a sequence of optional<T> copied from each non-null
-/// slot of the wrapped array (null slots result in empty optionals).
-template <typename T>
-class ViewArrayAs {
- private:
-  struct Get {
-    const uint8_t* validity;
-    const void* values;
-    int64_t offset;
-
-    internal::Maybe<T> operator()(int64_t i) const {
-      i += offset;
-      if (validity == nullptr || ArrowBitGet(validity, i)) {
-        if (std::is_same<T, bool>::value) {
-          return ArrowBitGet(static_cast<const uint8_t*>(values), i);
-        } else {
-          return static_cast<const T*>(values)[i];
-        }
-      }
-      return NA;
-    }
-  };
-
-  internal::RandomAccessRange<Get> range_;
-
- public:
-  ViewArrayAs(const ArrowArrayView* array_view)
-      : range_{
-            Get{
-                array_view->buffer_views[0].data.as_uint8,
-                array_view->buffer_views[1].data.data,
-                array_view->offset,
-            },
-            array_view->length,
-        } {}
-
-  ViewArrayAs(const ArrowArray* array)
-      : range_{
-            Get{
-                static_cast<const uint8_t*>(array->buffers[0]),
-                array->buffers[1],
-                /*offset=*/0,
-            },
-            array->length,
-        } {}
-
-  using value_type = typename internal::RandomAccessRange<Get>::value_type;
-  using const_iterator = typename 
internal::RandomAccessRange<Get>::const_iterator;
-  const_iterator begin() const { return range_.begin(); }
-  const_iterator end() const { return range_.end(); }
-  value_type operator[](int64_t i) const { return range_.get(i); }
-};
-
-/// \brief A range-for compatible wrapper for ArrowArray of binary or utf8
-///
-/// Provides a sequence of optional<ArrowStringView> referencing each non-null
-/// slot of the wrapped array (null slots result in empty optionals). Large
-/// binary and utf8 arrays can be wrapped by specifying 64 instead of 32 for
-/// the template argument.
-template <int OffsetSize>
-class ViewArrayAsBytes {
- private:
-  static_assert(OffsetSize == 32 || OffsetSize == 64, "");
-  using OffsetType = typename std::conditional<OffsetSize == 32, int32_t, 
int64_t>::type;
-
-  struct Get {
-    const uint8_t* validity;
-    const void* offsets;
-    const char* data;
-    int64_t offset;
-
-    internal::Maybe<ArrowStringView> operator()(int64_t i) const {
-      i += offset;
-      auto* offsets = static_cast<const OffsetType*>(this->offsets);
-      if (validity == nullptr || ArrowBitGet(validity, i)) {
-        return ArrowStringView{data + offsets[i], offsets[i + 1] - offsets[i]};
-      }
-      return NA;
-    }
-  };
-
-  internal::RandomAccessRange<Get> range_;
-
- public:
-  ViewArrayAsBytes(const ArrowArrayView* array_view)
-      : range_{
-            Get{
-                array_view->buffer_views[0].data.as_uint8,
-                array_view->buffer_views[1].data.data,
-                array_view->buffer_views[2].data.as_char,
-                array_view->offset,
-            },
-            array_view->length,
-        } {}
-
-  ViewArrayAsBytes(const ArrowArray* array)
-      : range_{
-            Get{
-                static_cast<const uint8_t*>(array->buffers[0]),
-                array->buffers[1],
-                static_cast<const char*>(array->buffers[2]),
-                /*offset=*/0,
-            },
-            array->length,
-        } {}
-
-  using value_type = typename internal::RandomAccessRange<Get>::value_type;
-  using const_iterator = typename 
internal::RandomAccessRange<Get>::const_iterator;
-  const_iterator begin() const { return range_.begin(); }
-  const_iterator end() const { return range_.end(); }
-  value_type operator[](int64_t i) const { return range_.get(i); }
-};
-
-/// \brief A range-for compatible wrapper for ArrowArray of fixed size binary
-///
-/// Provides a sequence of optional<ArrowStringView> referencing each non-null
-/// slot of the wrapped array (null slots result in empty optionals).
-class ViewArrayAsFixedSizeBytes {
- private:
-  struct Get {
-    const uint8_t* validity;
-    const char* data;
-    int64_t offset;
-    int fixed_size;
-
-    internal::Maybe<ArrowStringView> operator()(int64_t i) const {
-      i += offset;
-      if (validity == nullptr || ArrowBitGet(validity, i)) {
-        return ArrowStringView{data + i * fixed_size, fixed_size};
-      }
-      return NA;
-    }
-  };
-
-  internal::RandomAccessRange<Get> range_;
-
- public:
-  ViewArrayAsFixedSizeBytes(const ArrowArrayView* array_view, int fixed_size)
-      : range_{
-            Get{
-                array_view->buffer_views[0].data.as_uint8,
-                array_view->buffer_views[1].data.as_char,
-                array_view->offset,
-                fixed_size,
-            },
-            array_view->length,
-        } {}
-
-  ViewArrayAsFixedSizeBytes(const ArrowArray* array, int fixed_size)
-      : range_{
-            Get{
-                static_cast<const uint8_t*>(array->buffers[0]),
-                static_cast<const char*>(array->buffers[1]),
-                /*offset=*/0,
-                fixed_size,
-            },
-            array->length,
-        } {}
-
-  using value_type = typename internal::RandomAccessRange<Get>::value_type;
-  using const_iterator = typename 
internal::RandomAccessRange<Get>::const_iterator;
-  const_iterator begin() const { return range_.begin(); }
-  const_iterator end() const { return range_.end(); }
-  value_type operator[](int64_t i) const { return range_.get(i); }
-};
-
-/// \brief A range-for compatible wrapper for ArrowArrayStream
-///
-/// Provides a sequence of ArrowArray& referencing the most recent array drawn
-/// from the wrapped stream. (Each array may be moved from if necessary.)
-/// When streams terminate due to an error, the error code and message are
-/// available for inspection through the code() and error() member functions
-/// respectively. Failure to inspect the error code will result in
-/// an assertion failure. The number of arrays drawn from the stream is also
-/// available through the count() member function.
-class ViewArrayStream {
- public:
-  ViewArrayStream(ArrowArrayStream* stream, ArrowErrorCode* code, ArrowError* 
error)
-      : code_{code}, error_{error} {
-    // Using a slightly more verbose constructor to silence a warning that 
occurs
-    // on some versions of MSVC.
-    range_.next.self = this;
-    range_.next.stream = stream;
-  }
-
-  ViewArrayStream(ArrowArrayStream* stream, ArrowError* error)
-      : ViewArrayStream{stream, &internal_code_, error} {}
-
-  ViewArrayStream(ArrowArrayStream* stream)
-      : ViewArrayStream{stream, &internal_code_, &internal_error_} {}
-
-  // disable copy/move of this view, since its error references may point into 
itself
-  ViewArrayStream(ViewArrayStream&&) = delete;
-  ViewArrayStream& operator=(ViewArrayStream&&) = delete;
-  ViewArrayStream(const ViewArrayStream&) = delete;
-  ViewArrayStream& operator=(const ViewArrayStream&) = delete;
-
-  // ensure the error code of this stream was accessed at least once
-  ~ViewArrayStream() { NANOARROW_DCHECK(code_was_accessed_); }
-
- private:
-  struct Next {
-    ViewArrayStream* self;
-    ArrowArrayStream* stream;
-    UniqueArray array;
-
-    ArrowArray* operator()() {
-      array.reset();
-      *self->code_ = ArrowArrayStreamGetNext(stream, array.get(), 
self->error_);
-
-      if (array->release != nullptr) {
-        NANOARROW_DCHECK(*self->code_ == NANOARROW_OK);
-        ++self->count_;
-        return array.get();
-      }
-
-      return nullptr;
-    }
-  };
-
-  internal::InputRange<Next> range_;
-  ArrowErrorCode* code_;
-  ArrowError* error_;
-  ArrowError internal_error_ = {};
-  ArrowErrorCode internal_code_;
-  bool code_was_accessed_ = false;
-  int count_ = 0;
-
- public:
-  using value_type = typename internal::InputRange<Next>::value_type;
-  using iterator = typename internal::InputRange<Next>::iterator;
-  iterator begin() { return range_.begin(); }
-  iterator end() { return range_.end(); }
-
-  /// The error code which caused this stream to terminate, if any.
-  ArrowErrorCode code() {
-    code_was_accessed_ = true;
-    return *code_;
-  }
-  /// The error message which caused this stream to terminate, if any.
-  ArrowError* error() { return error_; }
-
-  /// The number of arrays streamed so far.
-  int count() const { return count_; }
-};
-
-/// @}
-
-}  // namespace nanoarrow
-
-/// \brief Equality comparison operator between ArrowStringView
-/// \ingroup nanoarrow_hpp-string_view_helpers
-inline bool operator==(ArrowStringView l, ArrowStringView r) {
-  if (l.size_bytes != r.size_bytes) return false;
-  return memcmp(l.data, r.data, l.size_bytes) == 0;
-}
-
-#endif
+#include "nanoarrow/hpp/array_stream.hpp"
+#include "nanoarrow/hpp/buffer.hpp"
+#include "nanoarrow/hpp/exception.hpp"
+#include "nanoarrow/hpp/operators.hpp"
+#include "nanoarrow/hpp/unique.hpp"
+#include "nanoarrow/hpp/view.hpp"
diff --git a/src/nanoarrow/nanoarrow_config.h.in 
b/src/nanoarrow/nanoarrow_config.h.in
index b29709a9..695a986c 100644
--- a/src/nanoarrow/nanoarrow_config.h.in
+++ b/src/nanoarrow/nanoarrow_config.h.in
@@ -15,8 +15,8 @@
 // specific language governing permissions and limitations
 // under the License.
 
-#ifndef NANOARROW_BUILD_ID_H_INCLUDED
-#define NANOARROW_BUILD_ID_H_INCLUDED
+#ifndef NANOARROW_CONFIG_H_INCLUDED
+#define NANOARROW_CONFIG_H_INCLUDED
 
 #define NANOARROW_VERSION_MAJOR @NANOARROW_VERSION_MAJOR@
 #define NANOARROW_VERSION_MINOR @NANOARROW_VERSION_MINOR@
@@ -29,4 +29,11 @@
 
 @NANOARROW_NAMESPACE_DEFINE@
 
+#if !defined(NANOARROW_CXX_NAMESPACE)
+#define NANOARROW_CXX_NAMESPACE nanoarrow
+#endif
+
+#define NANOARROW_CXX_NAMESPACE_BEGIN namespace NANOARROW_CXX_NAMESPACE {
+#define NANOARROW_CXX_NAMESPACE_END }
+
 #endif

Reply via email to