[{"id":36562,"web_url":"https://patchwork.libcamera.org/comment/36562/","msgid":"<176190025954.567526.13311102380114903851@ping.linuxembedded.co.uk>","date":"2025-10-31T08:44:19","subject":"Re: [RFC PATCH v3 02/22] libcamera: controls: Add `ControlValueView`","submitter":{"id":4,"url":"https://patchwork.libcamera.org/api/people/4/","name":"Kieran Bingham","email":"kieran.bingham@ideasonboard.com"},"content":"Quoting Barnabás Pőcze (2025-10-30 16:57:56)\n> Add `ControlValueView`, which is a non-owning read-only handle to a control\n> value with a very similar interface.\n> \n> Signed-off-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com>\n> Reviewed-by: Paul Elder <paul.elder@ideasonboard.com>\n> ---\n> changes in v3:\n>   * reword some documentation\n> \n> changes in v2:\n>   * rewrite `ControlValue::toString()` in terms of `operator<<(std::ostream&, const ControlValueView&)\n>     to avoid duplication\n> ---\n>  include/libcamera/controls.h |  68 +++++++++\n>  src/libcamera/controls.cpp   | 268 +++++++++++++++++++++++++----------\n>  2 files changed, 258 insertions(+), 78 deletions(-)\n> \n> diff --git a/include/libcamera/controls.h b/include/libcamera/controls.h\n> index 1ee1971c4d..4af52b4f7d 100644\n> --- a/include/libcamera/controls.h\n> +++ b/include/libcamera/controls.h\n> @@ -8,6 +8,7 @@\n>  #pragma once\n>  \n>  #include <assert.h>\n> +#include <cstddef>\n>  #include <map>\n>  #include <optional>\n>  #include <stdint.h>\n> @@ -25,6 +26,7 @@\n>  namespace libcamera {\n>  \n>  class ControlValidator;\n> +class ControlValueView;\n>  \n>  enum ControlType {\n>         ControlTypeNone,\n> @@ -160,6 +162,8 @@ public:\n>                     value.data(), value.size(), sizeof(typename T::value_type));\n>         }\n>  \n> +       explicit ControlValue(const ControlValueView &cvv);\n> +\n>         ~ControlValue();\n>  \n>         ControlValue(const ControlValue &other);\n> @@ -247,6 +251,70 @@ private:\n>                  std::size_t numElements, std::size_t elementSize);\n>  };\n>  \n> +class ControlValueView\n> +{\n> +public:\n> +       constexpr ControlValueView() noexcept\n> +               : type_(ControlTypeNone)\n> +       {\n> +       }\n> +\n> +       ControlValueView(const ControlValue &cv) noexcept\n> +               : ControlValueView(cv.type(), cv.isArray(), cv.numElements(),\n> +                                  reinterpret_cast<const std::byte *>(cv.data().data()))\n> +       {\n> +       }\n> +\n> +#ifndef __DOXYGEN__\n> +       // TODO: should have restricted access?\n\nRestricted access to what ? Do you mean doxygen should? or this\nconstructor should be private?\n\nI think this is an internal detail of just how ControlValueView(const\nControlValue &cv) is constructed right? So can it does look like it\ncould be a private constructor...?\n\nAs this is public API I think keeping it minimal makes sense. It's\neasier to add than subtract :D\n\n> +       ControlValueView(ControlType type, bool isArray, std::size_t numElements,\n> +                        const std::byte *data) noexcept\n> +               : type_(type), isArray_(isArray), numElements_(numElements),\n> +                 data_(data)\n> +       {\n> +               assert(isArray || numElements == 1);\n> +       }\n> +#endif\n> +\n> +       [[nodiscard]] explicit operator bool() const { return type_ != ControlTypeNone; }\n> +       [[nodiscard]] ControlType type() const { return type_; }\n> +       [[nodiscard]] bool isNone() const { return type_ == ControlTypeNone; }\n> +       [[nodiscard]] bool isArray() const { return isArray_; }\n> +       [[nodiscard]] std::size_t numElements() const { return numElements_; }\n> +       [[nodiscard]] Span<const std::byte> data() const;\n> +\n> +       [[nodiscard]] bool operator==(const ControlValueView &other) const;\n> +\n> +       [[nodiscard]] bool operator!=(const ControlValueView &other) const\n> +       {\n> +               return !(*this == other);\n> +       }\n> +\n> +       template<typename T>\n> +       [[nodiscard]] auto get() const\n> +       {\n> +               using TypeInfo = details::control_type<std::remove_cv_t<T>>;\n> +\n> +               assert(type_ == TypeInfo::value);\n> +               assert(isArray_ == (TypeInfo::size > 0));\n> +\n> +               if constexpr (TypeInfo::size > 0) {\n> +                       return T(reinterpret_cast<const typename T::value_type *>(data().data()), numElements_);\n> +               } else {\n> +                       assert(numElements_ == 1);\n> +                       return *reinterpret_cast<const T *>(data().data());\n> +               }\n> +       }\n> +\n> +private:\n> +       ControlType type_ : 8;\n> +       bool isArray_ = false;\n> +       uint32_t numElements_ = 0;\n> +       const std::byte *data_ = nullptr;\n> +};\n> +\n> +std::ostream &operator<<(std::ostream &s, const ControlValueView &v);\n> +\n>  class ControlId\n>  {\n>  public:\n> diff --git a/src/libcamera/controls.cpp b/src/libcamera/controls.cpp\n> index 1e1b49e6bd..222030c434 100644\n> --- a/src/libcamera/controls.cpp\n> +++ b/src/libcamera/controls.cpp\n> @@ -106,6 +106,16 @@ ControlValue::ControlValue()\n>  {\n>  }\n>  \n> +/**\n> + * \\brief Construct a ControlValue from a ControlValueView\n> + */\n> +ControlValue::ControlValue(const ControlValueView &cvv)\n> +       : ControlValue()\n> +{\n> +       set(cvv.type(), cvv.isArray(), cvv.data().data(),\n> +           cvv.numElements(), ControlValueSize[cvv.type()]);\n> +}\n> +\n>  /**\n>   * \\fn template<typename T> T ControlValue::ControlValue(const T &value)\n>   * \\brief Construct a ControlValue of type T\n> @@ -213,84 +223,7 @@ Span<uint8_t> ControlValue::data()\n>   */\n>  std::string ControlValue::toString() const\n>  {\n> -       if (type_ == ControlTypeNone)\n> -               return \"<ValueType Error>\";\n> -\n> -       const uint8_t *data = ControlValue::data().data();\n> -\n> -       if (type_ == ControlTypeString)\n> -               return std::string(reinterpret_cast<const char *>(data),\n> -                                  numElements_);\n> -\n> -       std::string str(isArray_ ? \"[ \" : \"\");\n> -\n> -       for (unsigned int i = 0; i < numElements_; ++i) {\n> -               switch (type_) {\n> -               case ControlTypeBool: {\n> -                       const bool *value = reinterpret_cast<const bool *>(data);\n> -                       str += *value ? \"true\" : \"false\";\n> -                       break;\n> -               }\n> -               case ControlTypeByte: {\n> -                       const uint8_t *value = reinterpret_cast<const uint8_t *>(data);\n> -                       str += std::to_string(*value);\n> -                       break;\n> -               }\n> -               case ControlTypeUnsigned16: {\n> -                       const uint16_t *value = reinterpret_cast<const uint16_t *>(data);\n> -                       str += std::to_string(*value);\n> -                       break;\n> -               }\n> -               case ControlTypeUnsigned32: {\n> -                       const uint32_t *value = reinterpret_cast<const uint32_t *>(data);\n> -                       str += std::to_string(*value);\n> -                       break;\n> -               }\n> -               case ControlTypeInteger32: {\n> -                       const int32_t *value = reinterpret_cast<const int32_t *>(data);\n> -                       str += std::to_string(*value);\n> -                       break;\n> -               }\n> -               case ControlTypeInteger64: {\n> -                       const int64_t *value = reinterpret_cast<const int64_t *>(data);\n> -                       str += std::to_string(*value);\n> -                       break;\n> -               }\n> -               case ControlTypeFloat: {\n> -                       const float *value = reinterpret_cast<const float *>(data);\n> -                       str += std::to_string(*value);\n> -                       break;\n> -               }\n> -               case ControlTypeRectangle: {\n> -                       const Rectangle *value = reinterpret_cast<const Rectangle *>(data);\n> -                       str += value->toString();\n> -                       break;\n> -               }\n> -               case ControlTypeSize: {\n> -                       const Size *value = reinterpret_cast<const Size *>(data);\n> -                       str += value->toString();\n> -                       break;\n> -               }\n> -               case ControlTypePoint: {\n> -                       const Point *value = reinterpret_cast<const Point *>(data);\n> -                       str += value->toString();\n> -                       break;\n> -               }\n> -               case ControlTypeNone:\n> -               case ControlTypeString:\n> -                       break;\n> -               }\n> -\n> -               if (i + 1 != numElements_)\n> -                       str += \", \";\n> -\n> -               data += ControlValueSize[type_];\n> -       }\n> -\n> -       if (isArray_)\n> -               str += \" ]\";\n> -\n> -       return str;\n> +       return static_cast<std::ostringstream&&>(std::ostringstream{} << ControlValueView(*this)).str();\n>  }\n>  \n>  /**\n> @@ -395,6 +328,185 @@ void ControlValue::reserve(ControlType type, bool isArray, std::size_t numElemen\n>                 storage_ = reinterpret_cast<void *>(new uint8_t[newSize]);\n>  }\n>  \n> +/**\n> + * \\fn ControlValueView::ControlValueView()\n> + * \\brief Construct an empty view\n> + * \\sa ControlValue::ControlValue()\n> + */\n> +\n> +/**\n> + * \\fn ControlValueView::ControlValueView(const ControlValue &v)\n> + * \\brief Construct a view referring to \\a v\n> + *\n> + * The constructed view will refer to the value stored by \\a v, and thus\n> + * \\a v must not be modified or destroyed before the view is destroyed.\n> + *\n> + * \\sa ControlValue::ControlValue()\n> + */\n> +\n> +/**\n> + * \\fn ControlValueView::operator bool() const\n> + * \\brief Determine if the referenced ControlValue is valid\n> + * \\sa ControlValueView::isNone()\n> + */\n> +\n> +/**\n> + * \\fn ControlType ControlValueView::type() const\n> + * \\copydoc ControlValue::type()\n> + * \\sa ControlValue::type()\n> + */\n> +\n> +/**\n> + * \\fn ControlValueView::isNone() const\n> + * \\copydoc ControlValue::isNone()\n> + * \\sa ControlValue::isNone()\n> + */\n> +\n> +/**\n> + * \\fn ControlValueView::isArray() const\n> + * \\copydoc ControlValue::isArray()\n> + * \\sa ControlValue::isArray()\n> + */\n> +\n> +/**\n> + * \\fn ControlValueView::numElements() const\n> + * \\copydoc ControlValue::numElements()\n> + * \\sa ControlValue::numElements()\n> + */\n> +\n> +/**\n> + * \\copydoc ControlValue::data()\n> + * \\sa ControlValue::data()\n> + */\n> +Span<const std::byte> ControlValueView::data() const\n> +{\n> +       return { data_, numElements_ * ControlValueSize[type_] };\n> +}\n> +\n> +/**\n> + * \\copydoc ControlValue::operator==()\n> + * \\sa ControlValue::operator==()\n> + * \\sa ControlValueView::operator!=()\n> + */\n> +bool ControlValueView::operator==(const ControlValueView &other) const\n> +{\n> +       if (type_ != other.type_)\n> +               return false;\n> +\n> +       if (numElements_ != other.numElements_)\n> +               return false;\n> +\n> +       if (isArray_ != other.isArray_)\n> +               return false;\n> +\n> +       const auto d = data();\n> +\n> +       return memcmp(d.data(), other.data_, d.size_bytes()) == 0;\n> +}\n> +\n> +/**\n> + * \\fn ControlValueView::operator!=() const\n> + * \\copydoc ControlValue::operator!=()\n> + * \\sa ControlValue::operator!=()\n> + * \\sa ControlValueView::operator==()\n> + */\n> +\n> +/**\n> + * \\fn template<typename T> T ControlValueView::get() const\n> + * \\copydoc ControlValue::get()\n> + * \\sa ControlValue::get()\n> + */\n> +\n> +/**\n> + * \\brief Insert a text representation of a value into an output stream\n> + * \\sa ControlValue::toString()\n> + */\n> +std::ostream &operator<<(std::ostream &s, const ControlValueView &v)\n> +{\n> +       const auto type = v.type();\n> +       if (type == ControlTypeNone)\n> +               return s << \"None\";\n> +\n> +       const auto *data = v.data().data();\n> +       const auto numElements = v.numElements();\n> +\n> +       if (type == ControlTypeString)\n> +               return s << std::string_view(reinterpret_cast<const char *>(data),\n> +                                            numElements);\n> +\n> +       const bool isArray = v.isArray();\n> +       if (isArray)\n> +               s << \"[ \";\n> +\n> +       for (std::size_t i = 0; i < numElements; ++i) {\n> +               if (i > 0)\n> +                       s << \", \";\n> +\n> +               switch (type) {\n> +               case ControlTypeBool: {\n> +                       const bool *value = reinterpret_cast<const bool *>(data);\n> +                       s << (*value ? \"true\" : \"false\");\n> +                       break;\n> +               }\n> +               case ControlTypeByte: {\n> +                       const auto *value = reinterpret_cast<const uint8_t *>(data);\n> +                       s << static_cast<unsigned int>(*value);\n> +                       break;\n> +               }\n> +               case ControlTypeUnsigned16: {\n> +                       const auto *value = reinterpret_cast<const uint16_t *>(data);\n> +                       s << *value;\n> +                       break;\n> +               }\n> +               case ControlTypeUnsigned32: {\n> +                       const auto *value = reinterpret_cast<const uint32_t *>(data);\n> +                       s << *value;\n> +                       break;\n> +               }\n> +               case ControlTypeInteger32: {\n> +                       const auto *value = reinterpret_cast<const int32_t *>(data);\n> +                       s << *value;\n> +                       break;\n> +               }\n> +               case ControlTypeInteger64: {\n> +                       const auto *value = reinterpret_cast<const int64_t *>(data);\n> +                       s << *value;\n> +                       break;\n> +               }\n> +               case ControlTypeFloat: {\n> +                       const auto *value = reinterpret_cast<const float *>(data);\n> +                       s << std::fixed << *value;\n> +                       break;\n> +               }\n> +               case ControlTypeRectangle: {\n> +                       const auto *value = reinterpret_cast<const Rectangle *>(data);\n> +                       s << *value;\n> +                       break;\n> +               }\n> +               case ControlTypeSize: {\n> +                       const auto *value = reinterpret_cast<const Size *>(data);\n> +                       s << *value;\n> +                       break;\n> +               }\n> +               case ControlTypePoint: {\n> +                       const auto *value = reinterpret_cast<const Point *>(data);\n> +                       s << *value;\n> +                       break;\n> +               }\n> +               case ControlTypeNone:\n> +               case ControlTypeString:\n\nhaha I was about to say what about strings - but it's handled early on\nso:\n\n\nReviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>\n\n> +                       break;\n> +               }\n> +\n> +               data += ControlValueSize[type];\n> +       }\n> +\n> +       if (isArray)\n> +               s << \" ]\";\n> +\n> +       return s;\n> +}\n> +\n>  /**\n>   * \\class ControlId\n>   * \\brief Control static metadata\n> -- \n> 2.51.1\n>","headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id 88F9EBE080\n\tfor <parsemail@patchwork.libcamera.org>;\n\tFri, 31 Oct 2025 08:44:24 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 575E460964;\n\tFri, 31 Oct 2025 09:44:23 +0100 (CET)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 554B9606E6\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri, 31 Oct 2025 09:44:22 +0100 (CET)","from pendragon.ideasonboard.com\n\t(cpc89244-aztw30-2-0-cust6594.18-1.cable.virginm.net [86.31.185.195])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 7945A1352;\n\tFri, 31 Oct 2025 09:42:31 +0100 (CET)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=ideasonboard.com header.i=@ideasonboard.com\n\theader.b=\"XMGu0gbS\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1761900151;\n\tbh=1gARcx1jYshEF4tpUaT4F4FVtv3bxwLjRth/j93dYIY=;\n\th=In-Reply-To:References:Subject:From:Cc:To:Date:From;\n\tb=XMGu0gbSocyW2TTwJ8AFQctqQooCDCfxZdZnh5vYoTds0rL33q1nzvVT+Ka2nwQmR\n\tFSe+qi61ODIpRKLgFxUZ5zEJZfH+glO8f8ZkCIfYuYyNVAKnoYwbXc1tU5ZWfymuk+\n\tq2klgwGMMERdr8VioV8j2AFHpCjKY6DCpZaxVc/4=","Content-Type":"text/plain; charset=\"utf-8\"","MIME-Version":"1.0","Content-Transfer-Encoding":"quoted-printable","In-Reply-To":"<20251030165816.1095180-3-barnabas.pocze@ideasonboard.com>","References":"<20251030165816.1095180-1-barnabas.pocze@ideasonboard.com>\n\t<20251030165816.1095180-3-barnabas.pocze@ideasonboard.com>","Subject":"Re: [RFC PATCH v3 02/22] libcamera: controls: Add `ControlValueView`","From":"Kieran Bingham <kieran.bingham@ideasonboard.com>","Cc":"Paul Elder <paul.elder@ideasonboard.com>","To":"=?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= <barnabas.pocze@ideasonboard.com>,\n\tlibcamera-devel@lists.libcamera.org","Date":"Fri, 31 Oct 2025 08:44:19 +0000","Message-ID":"<176190025954.567526.13311102380114903851@ping.linuxembedded.co.uk>","User-Agent":"alot/0.9.1","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}},{"id":36605,"web_url":"https://patchwork.libcamera.org/comment/36605/","msgid":"<049c379e-db0e-4711-9f01-f118d9a4f2d1@ideasonboard.com>","date":"2025-11-02T08:16:59","subject":"Re: [RFC PATCH v3 02/22] libcamera: controls: Add `ControlValueView`","submitter":{"id":156,"url":"https://patchwork.libcamera.org/api/people/156/","name":"Dan Scally","email":"dan.scally@ideasonboard.com"},"content":"Hi Barnabas\n\nOn 30/10/2025 16:57, Barnabás Pőcze wrote:\n> Add `ControlValueView`, which is a non-owning read-only handle to a control\n> value with a very similar interface.\n> \n> Signed-off-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com>\n> Reviewed-by: Paul Elder <paul.elder@ideasonboard.com>\n> ---\n\nI think the code here is fine, but I don't quite understand the need for the new class over just \nusing ControlValue, what's it going to be used for?\n\n> changes in v3:\n>    * reword some documentation\n> \n> changes in v2:\n>    * rewrite `ControlValue::toString()` in terms of `operator<<(std::ostream&, const ControlValueView&)\n>      to avoid duplication\n> ---\n>   include/libcamera/controls.h |  68 +++++++++\n>   src/libcamera/controls.cpp   | 268 +++++++++++++++++++++++++----------\n>   2 files changed, 258 insertions(+), 78 deletions(-)\n> \n> diff --git a/include/libcamera/controls.h b/include/libcamera/controls.h\n> index 1ee1971c4d..4af52b4f7d 100644\n> --- a/include/libcamera/controls.h\n> +++ b/include/libcamera/controls.h\n> @@ -8,6 +8,7 @@\n>   #pragma once\n>   \n>   #include <assert.h>\n> +#include <cstddef>\n>   #include <map>\n>   #include <optional>\n>   #include <stdint.h>\n> @@ -25,6 +26,7 @@\n>   namespace libcamera {\n>   \n>   class ControlValidator;\n> +class ControlValueView;\n>   \n>   enum ControlType {\n>   \tControlTypeNone,\n> @@ -160,6 +162,8 @@ public:\n>   \t\t    value.data(), value.size(), sizeof(typename T::value_type));\n>   \t}\n>   \n> +\texplicit ControlValue(const ControlValueView &cvv);\n> +\n>   \t~ControlValue();\n>   \n>   \tControlValue(const ControlValue &other);\n> @@ -247,6 +251,70 @@ private:\n>   \t\t std::size_t numElements, std::size_t elementSize);\n>   };\n>   \n> +class ControlValueView\n> +{\n> +public:\n> +\tconstexpr ControlValueView() noexcept\n> +\t\t: type_(ControlTypeNone)\n> +\t{\n> +\t}\n> +\n> +\tControlValueView(const ControlValue &cv) noexcept\n> +\t\t: ControlValueView(cv.type(), cv.isArray(), cv.numElements(),\n> +\t\t\t\t   reinterpret_cast<const std::byte *>(cv.data().data()))\n> +\t{\n> +\t}\n> +\n> +#ifndef __DOXYGEN__\n> +\t// TODO: should have restricted access?\n> +\tControlValueView(ControlType type, bool isArray, std::size_t numElements,\n> +\t\t\t const std::byte *data) noexcept\n> +\t\t: type_(type), isArray_(isArray), numElements_(numElements),\n> +\t\t  data_(data)\n> +\t{\n> +\t\tassert(isArray || numElements == 1);\n> +\t}\n> +#endif\n> +\n> +\t[[nodiscard]] explicit operator bool() const { return type_ != ControlTypeNone; }\n> +\t[[nodiscard]] ControlType type() const { return type_; }\n> +\t[[nodiscard]] bool isNone() const { return type_ == ControlTypeNone; }\n> +\t[[nodiscard]] bool isArray() const { return isArray_; }\n> +\t[[nodiscard]] std::size_t numElements() const { return numElements_; }\n> +\t[[nodiscard]] Span<const std::byte> data() const;\n> +\n> +\t[[nodiscard]] bool operator==(const ControlValueView &other) const;\n> +\n> +\t[[nodiscard]] bool operator!=(const ControlValueView &other) const\n> +\t{\n> +\t\treturn !(*this == other);\n> +\t}\n> +\n> +\ttemplate<typename T>\n> +\t[[nodiscard]] auto get() const\n> +\t{\n> +\t\tusing TypeInfo = details::control_type<std::remove_cv_t<T>>;\n> +\n> +\t\tassert(type_ == TypeInfo::value);\n> +\t\tassert(isArray_ == (TypeInfo::size > 0));\n> +\n> +\t\tif constexpr (TypeInfo::size > 0) {\n> +\t\t\treturn T(reinterpret_cast<const typename T::value_type *>(data().data()), numElements_);\n> +\t\t} else {\n> +\t\t\tassert(numElements_ == 1);\n> +\t\t\treturn *reinterpret_cast<const T *>(data().data());\n> +\t\t}\n> +\t}\n> +\n> +private:\n> +\tControlType type_ : 8;\n> +\tbool isArray_ = false;\n> +\tuint32_t numElements_ = 0;\n> +\tconst std::byte *data_ = nullptr;\n> +};\n> +\n> +std::ostream &operator<<(std::ostream &s, const ControlValueView &v);\n> +\n>   class ControlId\n>   {\n>   public:\n> diff --git a/src/libcamera/controls.cpp b/src/libcamera/controls.cpp\n> index 1e1b49e6bd..222030c434 100644\n> --- a/src/libcamera/controls.cpp\n> +++ b/src/libcamera/controls.cpp\n> @@ -106,6 +106,16 @@ ControlValue::ControlValue()\n>   {\n>   }\n>   \n> +/**\n> + * \\brief Construct a ControlValue from a ControlValueView\n> + */\n> +ControlValue::ControlValue(const ControlValueView &cvv)\n> +\t: ControlValue()\n> +{\n> +\tset(cvv.type(), cvv.isArray(), cvv.data().data(),\n> +\t    cvv.numElements(), ControlValueSize[cvv.type()]);\n> +}\n> +\n>   /**\n>    * \\fn template<typename T> T ControlValue::ControlValue(const T &value)\n>    * \\brief Construct a ControlValue of type T\n> @@ -213,84 +223,7 @@ Span<uint8_t> ControlValue::data()\n>    */\n>   std::string ControlValue::toString() const\n>   {\n> -\tif (type_ == ControlTypeNone)\n> -\t\treturn \"<ValueType Error>\";\n> -\n> -\tconst uint8_t *data = ControlValue::data().data();\n> -\n> -\tif (type_ == ControlTypeString)\n> -\t\treturn std::string(reinterpret_cast<const char *>(data),\n> -\t\t\t\t   numElements_);\n> -\n> -\tstd::string str(isArray_ ? \"[ \" : \"\");\n> -\n> -\tfor (unsigned int i = 0; i < numElements_; ++i) {\n> -\t\tswitch (type_) {\n> -\t\tcase ControlTypeBool: {\n> -\t\t\tconst bool *value = reinterpret_cast<const bool *>(data);\n> -\t\t\tstr += *value ? \"true\" : \"false\";\n> -\t\t\tbreak;\n> -\t\t}\n> -\t\tcase ControlTypeByte: {\n> -\t\t\tconst uint8_t *value = reinterpret_cast<const uint8_t *>(data);\n> -\t\t\tstr += std::to_string(*value);\n> -\t\t\tbreak;\n> -\t\t}\n> -\t\tcase ControlTypeUnsigned16: {\n> -\t\t\tconst uint16_t *value = reinterpret_cast<const uint16_t *>(data);\n> -\t\t\tstr += std::to_string(*value);\n> -\t\t\tbreak;\n> -\t\t}\n> -\t\tcase ControlTypeUnsigned32: {\n> -\t\t\tconst uint32_t *value = reinterpret_cast<const uint32_t *>(data);\n> -\t\t\tstr += std::to_string(*value);\n> -\t\t\tbreak;\n> -\t\t}\n> -\t\tcase ControlTypeInteger32: {\n> -\t\t\tconst int32_t *value = reinterpret_cast<const int32_t *>(data);\n> -\t\t\tstr += std::to_string(*value);\n> -\t\t\tbreak;\n> -\t\t}\n> -\t\tcase ControlTypeInteger64: {\n> -\t\t\tconst int64_t *value = reinterpret_cast<const int64_t *>(data);\n> -\t\t\tstr += std::to_string(*value);\n> -\t\t\tbreak;\n> -\t\t}\n> -\t\tcase ControlTypeFloat: {\n> -\t\t\tconst float *value = reinterpret_cast<const float *>(data);\n> -\t\t\tstr += std::to_string(*value);\n> -\t\t\tbreak;\n> -\t\t}\n> -\t\tcase ControlTypeRectangle: {\n> -\t\t\tconst Rectangle *value = reinterpret_cast<const Rectangle *>(data);\n> -\t\t\tstr += value->toString();\n> -\t\t\tbreak;\n> -\t\t}\n> -\t\tcase ControlTypeSize: {\n> -\t\t\tconst Size *value = reinterpret_cast<const Size *>(data);\n> -\t\t\tstr += value->toString();\n> -\t\t\tbreak;\n> -\t\t}\n> -\t\tcase ControlTypePoint: {\n> -\t\t\tconst Point *value = reinterpret_cast<const Point *>(data);\n> -\t\t\tstr += value->toString();\n> -\t\t\tbreak;\n> -\t\t}\n> -\t\tcase ControlTypeNone:\n> -\t\tcase ControlTypeString:\n> -\t\t\tbreak;\n> -\t\t}\n> -\n> -\t\tif (i + 1 != numElements_)\n> -\t\t\tstr += \", \";\n> -\n> -\t\tdata += ControlValueSize[type_];\n> -\t}\n> -\n> -\tif (isArray_)\n> -\t\tstr += \" ]\";\n> -\n> -\treturn str;\n> +\treturn static_cast<std::ostringstream&&>(std::ostringstream{} << ControlValueView(*this)).str();\n>   }\n>   \n>   /**\n> @@ -395,6 +328,185 @@ void ControlValue::reserve(ControlType type, bool isArray, std::size_t numElemen\n>   \t\tstorage_ = reinterpret_cast<void *>(new uint8_t[newSize]);\n>   }\n>   \n> +/**\n> + * \\fn ControlValueView::ControlValueView()\n> + * \\brief Construct an empty view\n> + * \\sa ControlValue::ControlValue()\n> + */\n> +\n> +/**\n> + * \\fn ControlValueView::ControlValueView(const ControlValue &v)\n> + * \\brief Construct a view referring to \\a v\n> + *\n> + * The constructed view will refer to the value stored by \\a v, and thus\n> + * \\a v must not be modified or destroyed before the view is destroyed.\n> + *\n> + * \\sa ControlValue::ControlValue()\n> + */\n> +\n> +/**\n> + * \\fn ControlValueView::operator bool() const\n> + * \\brief Determine if the referenced ControlValue is valid\n> + * \\sa ControlValueView::isNone()\n> + */\n> +\n> +/**\n> + * \\fn ControlType ControlValueView::type() const\n> + * \\copydoc ControlValue::type()\n> + * \\sa ControlValue::type()\n> + */\n> +\n> +/**\n> + * \\fn ControlValueView::isNone() const\n> + * \\copydoc ControlValue::isNone()\n> + * \\sa ControlValue::isNone()\n> + */\n> +\n> +/**\n> + * \\fn ControlValueView::isArray() const\n> + * \\copydoc ControlValue::isArray()\n> + * \\sa ControlValue::isArray()\n> + */\n> +\n> +/**\n> + * \\fn ControlValueView::numElements() const\n> + * \\copydoc ControlValue::numElements()\n> + * \\sa ControlValue::numElements()\n> + */\n> +\n> +/**\n> + * \\copydoc ControlValue::data()\n> + * \\sa ControlValue::data()\n> + */\n> +Span<const std::byte> ControlValueView::data() const\n> +{\n> +\treturn { data_, numElements_ * ControlValueSize[type_] };\n> +}\n> +\n> +/**\n> + * \\copydoc ControlValue::operator==()\n> + * \\sa ControlValue::operator==()\n> + * \\sa ControlValueView::operator!=()\n> + */\n> +bool ControlValueView::operator==(const ControlValueView &other) const\n> +{\n> +\tif (type_ != other.type_)\n> +\t\treturn false;\n> +\n> +\tif (numElements_ != other.numElements_)\n> +\t\treturn false;\n> +\n> +\tif (isArray_ != other.isArray_)\n> +\t\treturn false;\n> +\n> +\tconst auto d = data();\n> +\n> +\treturn memcmp(d.data(), other.data_, d.size_bytes()) == 0;\n> +}\n> +\n> +/**\n> + * \\fn ControlValueView::operator!=() const\n> + * \\copydoc ControlValue::operator!=()\n> + * \\sa ControlValue::operator!=()\n> + * \\sa ControlValueView::operator==()\n> + */\n> +\n> +/**\n> + * \\fn template<typename T> T ControlValueView::get() const\n> + * \\copydoc ControlValue::get()\n> + * \\sa ControlValue::get()\n> + */\n> +\n> +/**\n> + * \\brief Insert a text representation of a value into an output stream\n> + * \\sa ControlValue::toString()\n> + */\n> +std::ostream &operator<<(std::ostream &s, const ControlValueView &v)\n> +{\n> +\tconst auto type = v.type();\n> +\tif (type == ControlTypeNone)\n> +\t\treturn s << \"None\";\n> +\n> +\tconst auto *data = v.data().data();\n> +\tconst auto numElements = v.numElements();\n> +\n> +\tif (type == ControlTypeString)\n> +\t\treturn s << std::string_view(reinterpret_cast<const char *>(data),\n> +\t\t\t\t\t     numElements);\n> +\n> +\tconst bool isArray = v.isArray();\n> +\tif (isArray)\n> +\t\ts << \"[ \";\n> +\n> +\tfor (std::size_t i = 0; i < numElements; ++i) {\n> +\t\tif (i > 0)\n> +\t\t\ts << \", \";\n> +\n> +\t\tswitch (type) {\n> +\t\tcase ControlTypeBool: {\n> +\t\t\tconst bool *value = reinterpret_cast<const bool *>(data);\n> +\t\t\ts << (*value ? \"true\" : \"false\");\n> +\t\t\tbreak;\n> +\t\t}\n> +\t\tcase ControlTypeByte: {\n> +\t\t\tconst auto *value = reinterpret_cast<const uint8_t *>(data);\n> +\t\t\ts << static_cast<unsigned int>(*value);\n> +\t\t\tbreak;\n> +\t\t}\n> +\t\tcase ControlTypeUnsigned16: {\n> +\t\t\tconst auto *value = reinterpret_cast<const uint16_t *>(data);\n> +\t\t\ts << *value;\n> +\t\t\tbreak;\n> +\t\t}\n> +\t\tcase ControlTypeUnsigned32: {\n> +\t\t\tconst auto *value = reinterpret_cast<const uint32_t *>(data);\n> +\t\t\ts << *value;\n> +\t\t\tbreak;\n> +\t\t}\n> +\t\tcase ControlTypeInteger32: {\n> +\t\t\tconst auto *value = reinterpret_cast<const int32_t *>(data);\n> +\t\t\ts << *value;\n> +\t\t\tbreak;\n> +\t\t}\n> +\t\tcase ControlTypeInteger64: {\n> +\t\t\tconst auto *value = reinterpret_cast<const int64_t *>(data);\n> +\t\t\ts << *value;\n> +\t\t\tbreak;\n> +\t\t}\n> +\t\tcase ControlTypeFloat: {\n> +\t\t\tconst auto *value = reinterpret_cast<const float *>(data);\n> +\t\t\ts << std::fixed << *value;\n> +\t\t\tbreak;\n> +\t\t}\n> +\t\tcase ControlTypeRectangle: {\n> +\t\t\tconst auto *value = reinterpret_cast<const Rectangle *>(data);\n> +\t\t\ts << *value;\n> +\t\t\tbreak;\n> +\t\t}\n> +\t\tcase ControlTypeSize: {\n> +\t\t\tconst auto *value = reinterpret_cast<const Size *>(data);\n> +\t\t\ts << *value;\n> +\t\t\tbreak;\n> +\t\t}\n> +\t\tcase ControlTypePoint: {\n> +\t\t\tconst auto *value = reinterpret_cast<const Point *>(data);\n> +\t\t\ts << *value;\n> +\t\t\tbreak;\n> +\t\t}\n> +\t\tcase ControlTypeNone:\n> +\t\tcase ControlTypeString:\n> +\t\t\tbreak;\n> +\t\t}\n> +\n> +\t\tdata += ControlValueSize[type];\n> +\t}\n> +\n> +\tif (isArray)\n> +\t\ts << \" ]\";\n> +\n> +\treturn s;\n> +}\n> +\n>   /**\n>    * \\class ControlId\n>    * \\brief Control static metadata","headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id 32F95BDE4C\n\tfor <parsemail@patchwork.libcamera.org>;\n\tSun,  2 Nov 2025 08:17:07 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 4C24160A80;\n\tSun,  2 Nov 2025 09:17:06 +0100 (CET)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 4C1A66069A\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tSun,  2 Nov 2025 09:17:04 +0100 (CET)","from [192.168.0.43]\n\t(cpc141996-chfd3-2-0-cust928.12-3.cable.virginm.net [86.13.91.161])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 3A59178E\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tSun,  2 Nov 2025 09:15:12 +0100 (CET)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=ideasonboard.com header.i=@ideasonboard.com\n\theader.b=\"fG8HjRbn\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1762071312;\n\tbh=CeoQgbr9u+G/TcP4wH37jo0TJ+uRdYabgYdwehyVtds=;\n\th=Date:Subject:To:References:From:In-Reply-To:From;\n\tb=fG8HjRbnYTw7fzKU4RCkUtCfj3ZlsKtU90UCOMN+eudEYMfhtCIxz/WFgTyLOuscO\n\tMXqtQV5MBFSB/tDGj0Tys9kQFDh1Jwk+ifc2IsR5EcTatEO3+sMOKwiCePVJPFywE2\n\tkOJ8IaPd3l8ksvE+8IMt0o51HvLKLznPvPYFPB6Q=","Message-ID":"<049c379e-db0e-4711-9f01-f118d9a4f2d1@ideasonboard.com>","Date":"Sun, 2 Nov 2025 08:16:59 +0000","MIME-Version":"1.0","User-Agent":"Mozilla Thunderbird","Subject":"Re: [RFC PATCH v3 02/22] libcamera: controls: Add `ControlValueView`","To":"libcamera-devel@lists.libcamera.org","References":"<20251030165816.1095180-1-barnabas.pocze@ideasonboard.com>\n\t<20251030165816.1095180-3-barnabas.pocze@ideasonboard.com>","Content-Language":"en-US","From":"Dan Scally <dan.scally@ideasonboard.com>","In-Reply-To":"<20251030165816.1095180-3-barnabas.pocze@ideasonboard.com>","Content-Type":"text/plain; charset=UTF-8; format=flowed","Content-Transfer-Encoding":"8bit","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}},{"id":36640,"web_url":"https://patchwork.libcamera.org/comment/36640/","msgid":"<7f027139-c3bb-471c-9c0c-779ac0531d66@ideasonboard.com>","date":"2025-11-03T09:56:10","subject":"Re: [RFC PATCH v3 02/22] libcamera: controls: Add `ControlValueView`","submitter":{"id":216,"url":"https://patchwork.libcamera.org/api/people/216/","name":"Barnabás Pőcze","email":"barnabas.pocze@ideasonboard.com"},"content":"Hi\n\n2025. 11. 02. 9:16 keltezéssel, Dan Scally írta:\n> Hi Barnabas\n> \n> On 30/10/2025 16:57, Barnabás Pőcze wrote:\n>> Add `ControlValueView`, which is a non-owning read-only handle to a control\n>> value with a very similar interface.\n>>\n>> Signed-off-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com>\n>> Reviewed-by: Paul Elder <paul.elder@ideasonboard.com>\n>> ---\n> \n> I think the code here is fine, but I don't quite understand the need for the new class over just using ControlValue, what's it going to be used for?\n\nThe idea is that MetadataList should manage the storage for each value in it,\nwhich conflicts with ControlValue, which itself also manages the storage\nfor the contained value. So ControlValueView is added to separate read-only\ninterface of ControlValue from the other parts, so that it can be used\nin situations where the underlying storage is managed externally.\n\nI believe `ControlValue` could technically be reused in this specific\ninstance, but it was not explored in depth.\n\n\n> \n>> changes in v3:\n>>    * reword some documentation\n>>\n>> changes in v2:\n>>    * rewrite `ControlValue::toString()` in terms of `operator<<(std::ostream&, const ControlValueView&)\n>>      to avoid duplication\n>> ---\n>>   include/libcamera/controls.h |  68 +++++++++\n>>   src/libcamera/controls.cpp   | 268 +++++++++++++++++++++++++----------\n>>   2 files changed, 258 insertions(+), 78 deletions(-)\n>>\n>> diff --git a/include/libcamera/controls.h b/include/libcamera/controls.h\n>> index 1ee1971c4d..4af52b4f7d 100644\n>> --- a/include/libcamera/controls.h\n>> +++ b/include/libcamera/controls.h\n>> @@ -8,6 +8,7 @@\n>>   #pragma once\n>>   #include <assert.h>\n>> +#include <cstddef>\n>>   #include <map>\n>>   #include <optional>\n>>   #include <stdint.h>\n>> @@ -25,6 +26,7 @@\n>>   namespace libcamera {\n>>   class ControlValidator;\n>> +class ControlValueView;\n>>   enum ControlType {\n>>       ControlTypeNone,\n>> @@ -160,6 +162,8 @@ public:\n>>               value.data(), value.size(), sizeof(typename T::value_type));\n>>       }\n>> +    explicit ControlValue(const ControlValueView &cvv);\n>> +\n>>       ~ControlValue();\n>>       ControlValue(const ControlValue &other);\n>> @@ -247,6 +251,70 @@ private:\n>>            std::size_t numElements, std::size_t elementSize);\n>>   };\n>> +class ControlValueView\n>> +{\n>> +public:\n>> +    constexpr ControlValueView() noexcept\n>> +        : type_(ControlTypeNone)\n>> +    {\n>> +    }\n>> +\n>> +    ControlValueView(const ControlValue &cv) noexcept\n>> +        : ControlValueView(cv.type(), cv.isArray(), cv.numElements(),\n>> +                   reinterpret_cast<const std::byte *>(cv.data().data()))\n>> +    {\n>> +    }\n>> +\n>> +#ifndef __DOXYGEN__\n>> +    // TODO: should have restricted access?\n>> +    ControlValueView(ControlType type, bool isArray, std::size_t numElements,\n>> +             const std::byte *data) noexcept\n>> +        : type_(type), isArray_(isArray), numElements_(numElements),\n>> +          data_(data)\n>> +    {\n>> +        assert(isArray || numElements == 1);\n>> +    }\n>> +#endif\n>> +\n>> +    [[nodiscard]] explicit operator bool() const { return type_ != ControlTypeNone; }\n>> +    [[nodiscard]] ControlType type() const { return type_; }\n>> +    [[nodiscard]] bool isNone() const { return type_ == ControlTypeNone; }\n>> +    [[nodiscard]] bool isArray() const { return isArray_; }\n>> +    [[nodiscard]] std::size_t numElements() const { return numElements_; }\n>> +    [[nodiscard]] Span<const std::byte> data() const;\n>> +\n>> +    [[nodiscard]] bool operator==(const ControlValueView &other) const;\n>> +\n>> +    [[nodiscard]] bool operator!=(const ControlValueView &other) const\n>> +    {\n>> +        return !(*this == other);\n>> +    }\n>> +\n>> +    template<typename T>\n>> +    [[nodiscard]] auto get() const\n>> +    {\n>> +        using TypeInfo = details::control_type<std::remove_cv_t<T>>;\n>> +\n>> +        assert(type_ == TypeInfo::value);\n>> +        assert(isArray_ == (TypeInfo::size > 0));\n>> +\n>> +        if constexpr (TypeInfo::size > 0) {\n>> +            return T(reinterpret_cast<const typename T::value_type *>(data().data()), numElements_);\n>> +        } else {\n>> +            assert(numElements_ == 1);\n>> +            return *reinterpret_cast<const T *>(data().data());\n>> +        }\n>> +    }\n>> +\n>> +private:\n>> +    ControlType type_ : 8;\n>> +    bool isArray_ = false;\n>> +    uint32_t numElements_ = 0;\n>> +    const std::byte *data_ = nullptr;\n>> +};\n>> +\n>> +std::ostream &operator<<(std::ostream &s, const ControlValueView &v);\n>> +\n>>   class ControlId\n>>   {\n>>   public:\n>> diff --git a/src/libcamera/controls.cpp b/src/libcamera/controls.cpp\n>> index 1e1b49e6bd..222030c434 100644\n>> --- a/src/libcamera/controls.cpp\n>> +++ b/src/libcamera/controls.cpp\n>> @@ -106,6 +106,16 @@ ControlValue::ControlValue()\n>>   {\n>>   }\n>> +/**\n>> + * \\brief Construct a ControlValue from a ControlValueView\n>> + */\n>> +ControlValue::ControlValue(const ControlValueView &cvv)\n>> +    : ControlValue()\n>> +{\n>> +    set(cvv.type(), cvv.isArray(), cvv.data().data(),\n>> +        cvv.numElements(), ControlValueSize[cvv.type()]);\n>> +}\n>> +\n>>   /**\n>>    * \\fn template<typename T> T ControlValue::ControlValue(const T &value)\n>>    * \\brief Construct a ControlValue of type T\n>> @@ -213,84 +223,7 @@ Span<uint8_t> ControlValue::data()\n>>    */\n>>   std::string ControlValue::toString() const\n>>   {\n>> -    if (type_ == ControlTypeNone)\n>> -        return \"<ValueType Error>\";\n>> -\n>> -    const uint8_t *data = ControlValue::data().data();\n>> -\n>> -    if (type_ == ControlTypeString)\n>> -        return std::string(reinterpret_cast<const char *>(data),\n>> -                   numElements_);\n>> -\n>> -    std::string str(isArray_ ? \"[ \" : \"\");\n>> -\n>> -    for (unsigned int i = 0; i < numElements_; ++i) {\n>> -        switch (type_) {\n>> -        case ControlTypeBool: {\n>> -            const bool *value = reinterpret_cast<const bool *>(data);\n>> -            str += *value ? \"true\" : \"false\";\n>> -            break;\n>> -        }\n>> -        case ControlTypeByte: {\n>> -            const uint8_t *value = reinterpret_cast<const uint8_t *>(data);\n>> -            str += std::to_string(*value);\n>> -            break;\n>> -        }\n>> -        case ControlTypeUnsigned16: {\n>> -            const uint16_t *value = reinterpret_cast<const uint16_t *>(data);\n>> -            str += std::to_string(*value);\n>> -            break;\n>> -        }\n>> -        case ControlTypeUnsigned32: {\n>> -            const uint32_t *value = reinterpret_cast<const uint32_t *>(data);\n>> -            str += std::to_string(*value);\n>> -            break;\n>> -        }\n>> -        case ControlTypeInteger32: {\n>> -            const int32_t *value = reinterpret_cast<const int32_t *>(data);\n>> -            str += std::to_string(*value);\n>> -            break;\n>> -        }\n>> -        case ControlTypeInteger64: {\n>> -            const int64_t *value = reinterpret_cast<const int64_t *>(data);\n>> -            str += std::to_string(*value);\n>> -            break;\n>> -        }\n>> -        case ControlTypeFloat: {\n>> -            const float *value = reinterpret_cast<const float *>(data);\n>> -            str += std::to_string(*value);\n>> -            break;\n>> -        }\n>> -        case ControlTypeRectangle: {\n>> -            const Rectangle *value = reinterpret_cast<const Rectangle *>(data);\n>> -            str += value->toString();\n>> -            break;\n>> -        }\n>> -        case ControlTypeSize: {\n>> -            const Size *value = reinterpret_cast<const Size *>(data);\n>> -            str += value->toString();\n>> -            break;\n>> -        }\n>> -        case ControlTypePoint: {\n>> -            const Point *value = reinterpret_cast<const Point *>(data);\n>> -            str += value->toString();\n>> -            break;\n>> -        }\n>> -        case ControlTypeNone:\n>> -        case ControlTypeString:\n>> -            break;\n>> -        }\n>> -\n>> -        if (i + 1 != numElements_)\n>> -            str += \", \";\n>> -\n>> -        data += ControlValueSize[type_];\n>> -    }\n>> -\n>> -    if (isArray_)\n>> -        str += \" ]\";\n>> -\n>> -    return str;\n>> +    return static_cast<std::ostringstream&&>(std::ostringstream{} << ControlValueView(*this)).str();\n>>   }\n>>   /**\n>> @@ -395,6 +328,185 @@ void ControlValue::reserve(ControlType type, bool isArray, std::size_t numElemen\n>>           storage_ = reinterpret_cast<void *>(new uint8_t[newSize]);\n>>   }\n>> +/**\n>> + * \\fn ControlValueView::ControlValueView()\n>> + * \\brief Construct an empty view\n>> + * \\sa ControlValue::ControlValue()\n>> + */\n>> +\n>> +/**\n>> + * \\fn ControlValueView::ControlValueView(const ControlValue &v)\n>> + * \\brief Construct a view referring to \\a v\n>> + *\n>> + * The constructed view will refer to the value stored by \\a v, and thus\n>> + * \\a v must not be modified or destroyed before the view is destroyed.\n>> + *\n>> + * \\sa ControlValue::ControlValue()\n>> + */\n>> +\n>> +/**\n>> + * \\fn ControlValueView::operator bool() const\n>> + * \\brief Determine if the referenced ControlValue is valid\n>> + * \\sa ControlValueView::isNone()\n>> + */\n>> +\n>> +/**\n>> + * \\fn ControlType ControlValueView::type() const\n>> + * \\copydoc ControlValue::type()\n>> + * \\sa ControlValue::type()\n>> + */\n>> +\n>> +/**\n>> + * \\fn ControlValueView::isNone() const\n>> + * \\copydoc ControlValue::isNone()\n>> + * \\sa ControlValue::isNone()\n>> + */\n>> +\n>> +/**\n>> + * \\fn ControlValueView::isArray() const\n>> + * \\copydoc ControlValue::isArray()\n>> + * \\sa ControlValue::isArray()\n>> + */\n>> +\n>> +/**\n>> + * \\fn ControlValueView::numElements() const\n>> + * \\copydoc ControlValue::numElements()\n>> + * \\sa ControlValue::numElements()\n>> + */\n>> +\n>> +/**\n>> + * \\copydoc ControlValue::data()\n>> + * \\sa ControlValue::data()\n>> + */\n>> +Span<const std::byte> ControlValueView::data() const\n>> +{\n>> +    return { data_, numElements_ * ControlValueSize[type_] };\n>> +}\n>> +\n>> +/**\n>> + * \\copydoc ControlValue::operator==()\n>> + * \\sa ControlValue::operator==()\n>> + * \\sa ControlValueView::operator!=()\n>> + */\n>> +bool ControlValueView::operator==(const ControlValueView &other) const\n>> +{\n>> +    if (type_ != other.type_)\n>> +        return false;\n>> +\n>> +    if (numElements_ != other.numElements_)\n>> +        return false;\n>> +\n>> +    if (isArray_ != other.isArray_)\n>> +        return false;\n>> +\n>> +    const auto d = data();\n>> +\n>> +    return memcmp(d.data(), other.data_, d.size_bytes()) == 0;\n>> +}\n>> +\n>> +/**\n>> + * \\fn ControlValueView::operator!=() const\n>> + * \\copydoc ControlValue::operator!=()\n>> + * \\sa ControlValue::operator!=()\n>> + * \\sa ControlValueView::operator==()\n>> + */\n>> +\n>> +/**\n>> + * \\fn template<typename T> T ControlValueView::get() const\n>> + * \\copydoc ControlValue::get()\n>> + * \\sa ControlValue::get()\n>> + */\n>> +\n>> +/**\n>> + * \\brief Insert a text representation of a value into an output stream\n>> + * \\sa ControlValue::toString()\n>> + */\n>> +std::ostream &operator<<(std::ostream &s, const ControlValueView &v)\n>> +{\n>> +    const auto type = v.type();\n>> +    if (type == ControlTypeNone)\n>> +        return s << \"None\";\n>> +\n>> +    const auto *data = v.data().data();\n>> +    const auto numElements = v.numElements();\n>> +\n>> +    if (type == ControlTypeString)\n>> +        return s << std::string_view(reinterpret_cast<const char *>(data),\n>> +                         numElements);\n>> +\n>> +    const bool isArray = v.isArray();\n>> +    if (isArray)\n>> +        s << \"[ \";\n>> +\n>> +    for (std::size_t i = 0; i < numElements; ++i) {\n>> +        if (i > 0)\n>> +            s << \", \";\n>> +\n>> +        switch (type) {\n>> +        case ControlTypeBool: {\n>> +            const bool *value = reinterpret_cast<const bool *>(data);\n>> +            s << (*value ? \"true\" : \"false\");\n>> +            break;\n>> +        }\n>> +        case ControlTypeByte: {\n>> +            const auto *value = reinterpret_cast<const uint8_t *>(data);\n>> +            s << static_cast<unsigned int>(*value);\n>> +            break;\n>> +        }\n>> +        case ControlTypeUnsigned16: {\n>> +            const auto *value = reinterpret_cast<const uint16_t *>(data);\n>> +            s << *value;\n>> +            break;\n>> +        }\n>> +        case ControlTypeUnsigned32: {\n>> +            const auto *value = reinterpret_cast<const uint32_t *>(data);\n>> +            s << *value;\n>> +            break;\n>> +        }\n>> +        case ControlTypeInteger32: {\n>> +            const auto *value = reinterpret_cast<const int32_t *>(data);\n>> +            s << *value;\n>> +            break;\n>> +        }\n>> +        case ControlTypeInteger64: {\n>> +            const auto *value = reinterpret_cast<const int64_t *>(data);\n>> +            s << *value;\n>> +            break;\n>> +        }\n>> +        case ControlTypeFloat: {\n>> +            const auto *value = reinterpret_cast<const float *>(data);\n>> +            s << std::fixed << *value;\n>> +            break;\n>> +        }\n>> +        case ControlTypeRectangle: {\n>> +            const auto *value = reinterpret_cast<const Rectangle *>(data);\n>> +            s << *value;\n>> +            break;\n>> +        }\n>> +        case ControlTypeSize: {\n>> +            const auto *value = reinterpret_cast<const Size *>(data);\n>> +            s << *value;\n>> +            break;\n>> +        }\n>> +        case ControlTypePoint: {\n>> +            const auto *value = reinterpret_cast<const Point *>(data);\n>> +            s << *value;\n>> +            break;\n>> +        }\n>> +        case ControlTypeNone:\n>> +        case ControlTypeString:\n>> +            break;\n>> +        }\n>> +\n>> +        data += ControlValueSize[type];\n>> +    }\n>> +\n>> +    if (isArray)\n>> +        s << \" ]\";\n>> +\n>> +    return s;\n>> +}\n>> +\n>>   /**\n>>    * \\class ControlId\n>>    * \\brief Control static metadata\n>","headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id 5B014BDE4C\n\tfor <parsemail@patchwork.libcamera.org>;\n\tMon,  3 Nov 2025 09:56:17 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 6E70460A80;\n\tMon,  3 Nov 2025 10:56:16 +0100 (CET)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 49EE660805\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon,  3 Nov 2025 10:56:15 +0100 (CET)","from [192.168.33.39] (185.221.140.239.nat.pool.zt.hu\n\t[185.221.140.239])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 277E699F;\n\tMon,  3 Nov 2025 10:54:22 +0100 (CET)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=ideasonboard.com header.i=@ideasonboard.com\n\theader.b=\"TzcfEKc8\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1762163662;\n\tbh=vStTSPrhtfqGaajeXmWoT2lQPyCUsKFX2XY5kwM5rF8=;\n\th=Date:Subject:To:References:From:In-Reply-To:From;\n\tb=TzcfEKc87E3DETfFXdLtcmdfqUzWGW17jW2ppyOy811L90wC4z6GiLlAq5DCO9O+5\n\tGtQMijxP/Z5BiLFWju+7sbJrYkDXlGolQivMVK2CqCHS+8q1JHHGCHxTjuDcBAMNJ1\n\tYJA1Y+cRH75/R5PEJrKeZCoJFIAwXGT96GeMeiEI=","Message-ID":"<7f027139-c3bb-471c-9c0c-779ac0531d66@ideasonboard.com>","Date":"Mon, 3 Nov 2025 10:56:10 +0100","MIME-Version":"1.0","User-Agent":"Mozilla Thunderbird","Subject":"Re: [RFC PATCH v3 02/22] libcamera: controls: Add `ControlValueView`","To":"Dan Scally <dan.scally@ideasonboard.com>,\n\tlibcamera-devel@lists.libcamera.org","References":"<20251030165816.1095180-1-barnabas.pocze@ideasonboard.com>\n\t<20251030165816.1095180-3-barnabas.pocze@ideasonboard.com>\n\t<049c379e-db0e-4711-9f01-f118d9a4f2d1@ideasonboard.com>","From":"=?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= <barnabas.pocze@ideasonboard.com>","Content-Language":"en-US, hu-HU","In-Reply-To":"<049c379e-db0e-4711-9f01-f118d9a4f2d1@ideasonboard.com>","Content-Type":"text/plain; charset=UTF-8; format=flowed","Content-Transfer-Encoding":"8bit","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}},{"id":36641,"web_url":"https://patchwork.libcamera.org/comment/36641/","msgid":"<569d2943-0a75-4d65-90c7-27a6707f2f7a@ideasonboard.com>","date":"2025-11-03T10:03:40","subject":"Re: [RFC PATCH v3 02/22] libcamera: controls: Add `ControlValueView`","submitter":{"id":216,"url":"https://patchwork.libcamera.org/api/people/216/","name":"Barnabás Pőcze","email":"barnabas.pocze@ideasonboard.com"},"content":"Hi\n\n2025. 10. 31. 9:44 keltezéssel, Kieran Bingham írta:\n> Quoting Barnabás Pőcze (2025-10-30 16:57:56)\n>> Add `ControlValueView`, which is a non-owning read-only handle to a control\n>> value with a very similar interface.\n>>\n>> Signed-off-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com>\n>> Reviewed-by: Paul Elder <paul.elder@ideasonboard.com>\n>> ---\n>> changes in v3:\n>>    * reword some documentation\n>>\n>> changes in v2:\n>>    * rewrite `ControlValue::toString()` in terms of `operator<<(std::ostream&, const ControlValueView&)\n>>      to avoid duplication\n>> ---\n>>   include/libcamera/controls.h |  68 +++++++++\n>>   src/libcamera/controls.cpp   | 268 +++++++++++++++++++++++++----------\n>>   2 files changed, 258 insertions(+), 78 deletions(-)\n>>\n>> diff --git a/include/libcamera/controls.h b/include/libcamera/controls.h\n>> index 1ee1971c4d..4af52b4f7d 100644\n>> --- a/include/libcamera/controls.h\n>> +++ b/include/libcamera/controls.h\n>> @@ -8,6 +8,7 @@\n>>   #pragma once\n>>   \n>>   #include <assert.h>\n>> +#include <cstddef>\n>>   #include <map>\n>>   #include <optional>\n>>   #include <stdint.h>\n>> @@ -25,6 +26,7 @@\n>>   namespace libcamera {\n>>   \n>>   class ControlValidator;\n>> +class ControlValueView;\n>>   \n>>   enum ControlType {\n>>          ControlTypeNone,\n>> @@ -160,6 +162,8 @@ public:\n>>                      value.data(), value.size(), sizeof(typename T::value_type));\n>>          }\n>>   \n>> +       explicit ControlValue(const ControlValueView &cvv);\n>> +\n>>          ~ControlValue();\n>>   \n>>          ControlValue(const ControlValue &other);\n>> @@ -247,6 +251,70 @@ private:\n>>                   std::size_t numElements, std::size_t elementSize);\n>>   };\n>>   \n>> +class ControlValueView\n>> +{\n>> +public:\n>> +       constexpr ControlValueView() noexcept\n>> +               : type_(ControlTypeNone)\n>> +       {\n>> +       }\n>> +\n>> +       ControlValueView(const ControlValue &cv) noexcept\n>> +               : ControlValueView(cv.type(), cv.isArray(), cv.numElements(),\n>> +                                  reinterpret_cast<const std::byte *>(cv.data().data()))\n>> +       {\n>> +       }\n>> +\n>> +#ifndef __DOXYGEN__\n>> +       // TODO: should have restricted access?\n> \n> Restricted access to what ? Do you mean doxygen should? or this\n> constructor should be private?\n> \n> I think this is an internal detail of just how ControlValueView(const\n> ControlValue &cv) is constructed right? So can it does look like it\n> could be a private constructor...?\n> \n> As this is public API I think keeping it minimal makes sense. It's\n> easier to add than subtract :D\n\nIt should ideally be \"package private\". It is only used by `MetadataList`\nand related types, but I don't really want to add friend delcarations.\nI think I'll just document it as `\\internal` so it does not appear in the\npublic documentation.\n\n\n> \n>> +       ControlValueView(ControlType type, bool isArray, std::size_t numElements,\n>> +                        const std::byte *data) noexcept\n>> +               : type_(type), isArray_(isArray), numElements_(numElements),\n>> +                 data_(data)\n>> +       {\n>> +               assert(isArray || numElements == 1);\n>> +       }\n>> +#endif\n>> +\n> [...]","headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id E64E9C3241\n\tfor <parsemail@patchwork.libcamera.org>;\n\tMon,  3 Nov 2025 10:03:48 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 188CB609D8;\n\tMon,  3 Nov 2025 11:03:48 +0100 (CET)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 469C560805\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon,  3 Nov 2025 11:03:45 +0100 (CET)","from [192.168.33.39] (185.221.140.239.nat.pool.zt.hu\n\t[185.221.140.239])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 0BA1F99F;\n\tMon,  3 Nov 2025 11:01:51 +0100 (CET)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=ideasonboard.com header.i=@ideasonboard.com\n\theader.b=\"nCbzcMqh\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1762164112;\n\tbh=+NdvyyDlu8+DltpFMnHO46ir67LktHvYHXGKdu3RVaw=;\n\th=Date:Subject:To:Cc:References:From:In-Reply-To:From;\n\tb=nCbzcMqhQMkeSdLwdQjPNg+VdK6FrMzxK4QcZbn/GGm7VcfItZS7gdkdoTZs3i0wx\n\tMSxPhQ3QKix3pSivCWy3u42f+Pxz6tAZG5438anPXD6f/BbDO/WOhAdPCwX9WhItYR\n\tsl1Xt63rflOa/12AGLQIcsfotFM73X/0qtKC3J+8=","Message-ID":"<569d2943-0a75-4d65-90c7-27a6707f2f7a@ideasonboard.com>","Date":"Mon, 3 Nov 2025 11:03:40 +0100","MIME-Version":"1.0","User-Agent":"Mozilla Thunderbird","Subject":"Re: [RFC PATCH v3 02/22] libcamera: controls: Add `ControlValueView`","To":"Kieran Bingham <kieran.bingham@ideasonboard.com>,\n\tlibcamera-devel@lists.libcamera.org","Cc":"Paul Elder <paul.elder@ideasonboard.com>","References":"<20251030165816.1095180-1-barnabas.pocze@ideasonboard.com>\n\t<20251030165816.1095180-3-barnabas.pocze@ideasonboard.com>\n\t<176190025954.567526.13311102380114903851@ping.linuxembedded.co.uk>","From":"=?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= <barnabas.pocze@ideasonboard.com>","Content-Language":"en-US, hu-HU","In-Reply-To":"<176190025954.567526.13311102380114903851@ping.linuxembedded.co.uk>","Content-Type":"text/plain; charset=UTF-8; format=flowed","Content-Transfer-Encoding":"8bit","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}}]