[v1,2/2] libcamera: controls: Expose string controls as `std::string_view`
diff mbox series

Message ID 20250422124751.30409-3-barnabas.pocze@ideasonboard.com
State Superseded
Headers show
Series
  • libcamera: controls: String controls as `std::string_view`
Related show

Commit Message

Barnabás Pőcze April 22, 2025, 12:47 p.m. UTC
When retrieving the value from a `ControlValue` usually one of two
things happen: a small, trivially copyable object is returned by
value; or a view into the internal buffer is provided. This is true
for everything except strings, which are returned in `std::string`,
incurring the overhead of string construction.

To guarantee no potentially "expensive" copies, use `std::string_view`
pointing to the internal buffer to return the value. This is similar
to how other array-like types are returned with a `Span<>`.

This is an API break, but its scope is limited to just `properties::Model`.

Bug: https://bugs.libcamera.org/show_bug.cgi?id=256
Signed-off-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com>
---
 include/libcamera/control_ids.h.in  |  1 +
 include/libcamera/controls.h        | 15 ++++++++-------
 src/apps/cam/capture_script.cpp     |  2 +-
 src/apps/cam/main.cpp               |  2 +-
 src/apps/common/dng_writer.cpp      |  2 +-
 src/apps/qcam/cam_select_dialog.cpp |  6 +++---
 src/py/libcamera/py_helpers.cpp     |  4 ++--
 test/controls/control_value.cpp     |  4 ++--
 utils/codegen/controls.py           |  2 +-
 9 files changed, 20 insertions(+), 18 deletions(-)

Comments

Kieran Bingham April 23, 2025, 8:33 a.m. UTC | #1
Quoting Barnabás Pőcze (2025-04-22 13:47:51)
> When retrieving the value from a `ControlValue` usually one of two
> things happen: a small, trivially copyable object is returned by
> value; or a view into the internal buffer is provided. This is true
> for everything except strings, which are returned in `std::string`,
> incurring the overhead of string construction.
> 
> To guarantee no potentially "expensive" copies, use `std::string_view`
> pointing to the internal buffer to return the value. This is similar
> to how other array-like types are returned with a `Span<>`.
> 
> This is an API break, but its scope is limited to just `properties::Model`.

This breaks in CI at the build-history stage:

https://gitlab.freedesktop.org/camera/libcamera/-/jobs/75119329#L1034

I wonder, are we missing a dependency declaration somewhere that should
have otherwise caused something to get rebuilt? All of the other builds
seemed to have worked on that pipeline ?

> 
> Bug: https://bugs.libcamera.org/show_bug.cgi?id=256
> Signed-off-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com>
> ---
>  include/libcamera/control_ids.h.in  |  1 +
>  include/libcamera/controls.h        | 15 ++++++++-------
>  src/apps/cam/capture_script.cpp     |  2 +-
>  src/apps/cam/main.cpp               |  2 +-
>  src/apps/common/dng_writer.cpp      |  2 +-
>  src/apps/qcam/cam_select_dialog.cpp |  6 +++---
>  src/py/libcamera/py_helpers.cpp     |  4 ++--
>  test/controls/control_value.cpp     |  4 ++--
>  utils/codegen/controls.py           |  2 +-
>  9 files changed, 20 insertions(+), 18 deletions(-)
> 
> diff --git a/include/libcamera/control_ids.h.in b/include/libcamera/control_ids.h.in
> index 5d0594c68..41997f79f 100644
> --- a/include/libcamera/control_ids.h.in
> +++ b/include/libcamera/control_ids.h.in
> @@ -13,6 +13,7 @@
>  #include <map>
>  #include <stdint.h>
>  #include <string>
> +#include <string_view>
>  
>  #include <libcamera/controls.h>
>  
> diff --git a/include/libcamera/controls.h b/include/libcamera/controls.h
> index 4bfe9615c..275f45200 100644
> --- a/include/libcamera/controls.h
> +++ b/include/libcamera/controls.h
> @@ -13,6 +13,7 @@
>  #include <set>
>  #include <stdint.h>
>  #include <string>
> +#include <string_view>
>  #include <unordered_map>
>  #include <vector>
>  
> @@ -96,7 +97,7 @@ struct control_type<float> {
>  };
>  
>  template<>
> -struct control_type<std::string> {
> +struct control_type<std::string_view> {
>         static constexpr ControlType value = ControlTypeString;
>         static constexpr std::size_t size = 0;
>  };
> @@ -138,7 +139,7 @@ public:
>  #ifndef __DOXYGEN__
>         template<typename T, std::enable_if_t<!details::is_span<T>::value &&
>                                               details::control_type<T>::value &&
> -                                             !std::is_same<std::string, std::remove_cv_t<T>>::value,
> +                                             !std::is_same<std::string_view, std::remove_cv_t<T>>::value,
>                                               std::nullptr_t> = nullptr>
>         ControlValue(const T &value)
>                 : type_(ControlTypeNone), numElements_(0)
> @@ -148,7 +149,7 @@ public:
>         }
>  
>         template<typename T, std::enable_if_t<details::is_span<T>::value ||
> -                                             std::is_same<std::string, std::remove_cv_t<T>>::value,
> +                                             std::is_same<std::string_view, std::remove_cv_t<T>>::value,
>                                               std::nullptr_t> = nullptr>
>  #else
>         template<typename T>
> @@ -182,7 +183,7 @@ public:
>  
>  #ifndef __DOXYGEN__
>         template<typename T, std::enable_if_t<!details::is_span<T>::value &&
> -                                             !std::is_same<std::string, std::remove_cv_t<T>>::value,
> +                                             !std::is_same<std::string_view, std::remove_cv_t<T>>::value,
>                                               std::nullptr_t> = nullptr>
>         T get() const
>         {
> @@ -193,7 +194,7 @@ public:
>         }
>  
>         template<typename T, std::enable_if_t<details::is_span<T>::value ||
> -                                             std::is_same<std::string, std::remove_cv_t<T>>::value,
> +                                             std::is_same<std::string_view, std::remove_cv_t<T>>::value,
>                                               std::nullptr_t> = nullptr>
>  #else
>         template<typename T>
> @@ -210,7 +211,7 @@ public:
>  
>  #ifndef __DOXYGEN__
>         template<typename T, std::enable_if_t<!details::is_span<T>::value &&
> -                                             !std::is_same<std::string, std::remove_cv_t<T>>::value,
> +                                             !std::is_same<std::string_view, std::remove_cv_t<T>>::value,
>                                               std::nullptr_t> = nullptr>
>         void set(const T &value)
>         {
> @@ -219,7 +220,7 @@ public:
>         }
>  
>         template<typename T, std::enable_if_t<details::is_span<T>::value ||
> -                                             std::is_same<std::string, std::remove_cv_t<T>>::value,
> +                                             std::is_same<std::string_view, std::remove_cv_t<T>>::value,
>                                               std::nullptr_t> = nullptr>
>  #else
>         template<typename T>
> diff --git a/src/apps/cam/capture_script.cpp b/src/apps/cam/capture_script.cpp
> index fdf82efc0..bc4c7c3a0 100644
> --- a/src/apps/cam/capture_script.cpp
> +++ b/src/apps/cam/capture_script.cpp
> @@ -502,7 +502,7 @@ ControlValue CaptureScript::parseScalarControl(const ControlId *id,
>                 break;
>         }
>         case ControlTypeString: {
> -               value.set<std::string>(repr);
> +               value.set<std::string_view>(repr);
>                 break;
>         }
>         default:
> diff --git a/src/apps/cam/main.cpp b/src/apps/cam/main.cpp
> index fa266eca6..157d238d7 100644
> --- a/src/apps/cam/main.cpp
> +++ b/src/apps/cam/main.cpp
> @@ -337,7 +337,7 @@ std::string CamApp::cameraName(const Camera *camera)
>                  */
>                 const auto &model = props.get(properties::Model);
>                 if (model)
> -                       name = "'" + *model + "' ";
> +                       name.append("'").append(*model).append("' ");
>         }
>  
>         name += "(" + camera->id() + ")";
> diff --git a/src/apps/common/dng_writer.cpp b/src/apps/common/dng_writer.cpp
> index ac4619511..8d57023e1 100644
> --- a/src/apps/common/dng_writer.cpp
> +++ b/src/apps/common/dng_writer.cpp
> @@ -564,7 +564,7 @@ int DNGWriter::write(const char *filename, const Camera *camera,
>  
>         const auto &model = cameraProperties.get(properties::Model);
>         if (model) {
> -               TIFFSetField(tif, TIFFTAG_MODEL, model->c_str());
> +               TIFFSetField(tif, TIFFTAG_MODEL, std::string(*model).c_str());
>                 /* \todo set TIFFTAG_UNIQUECAMERAMODEL. */
>         }
>  
> diff --git a/src/apps/qcam/cam_select_dialog.cpp b/src/apps/qcam/cam_select_dialog.cpp
> index 6b6d0713c..9f25ba091 100644
> --- a/src/apps/qcam/cam_select_dialog.cpp
> +++ b/src/apps/qcam/cam_select_dialog.cpp
> @@ -114,8 +114,8 @@ void CameraSelectorDialog::updateCameraInfo(QString cameraId)
>                 cameraLocation_->setText("Unknown");
>         }
>  
> -       const auto &model = properties.get(libcamera::properties::Model)
> -                                   .value_or("Unknown");
> +       const auto model = properties.get(libcamera::properties::Model)
> +                                    .value_or("Unknown");
>  
> -       cameraModel_->setText(QString::fromStdString(model));
> +       cameraModel_->setText(QString::fromUtf8(model.data(), model.length()));
>  }
> diff --git a/src/py/libcamera/py_helpers.cpp b/src/py/libcamera/py_helpers.cpp
> index 1ad1d4c1a..8c55ef845 100644
> --- a/src/py/libcamera/py_helpers.cpp
> +++ b/src/py/libcamera/py_helpers.cpp
> @@ -47,7 +47,7 @@ py::object controlValueToPy(const ControlValue &cv)
>         case ControlTypeFloat:
>                 return valueOrTuple<float>(cv);
>         case ControlTypeString:
> -               return py::cast(cv.get<std::string>());
> +               return py::cast(cv.get<std::string_view>());
>         case ControlTypeSize: {
>                 const Size *v = reinterpret_cast<const Size *>(cv.data().data());
>                 return py::cast(v);
> @@ -88,7 +88,7 @@ ControlValue pyToControlValue(const py::object &ob, ControlType type)
>         case ControlTypeFloat:
>                 return controlValueMaybeArray<float>(ob);
>         case ControlTypeString:
> -               return ControlValue(ob.cast<std::string>());
> +               return ControlValue(ob.cast<std::string_view>());
>         case ControlTypeRectangle:
>                 return controlValueMaybeArray<Rectangle>(ob);
>         case ControlTypeSize:
> diff --git a/test/controls/control_value.cpp b/test/controls/control_value.cpp
> index 5084fd0cf..032050a77 100644
> --- a/test/controls/control_value.cpp
> +++ b/test/controls/control_value.cpp
> @@ -318,7 +318,7 @@ protected:
>                 /*
>                  * String type.
>                  */
> -               std::string string{ "libcamera" };
> +               std::string_view string{ "libcamera" };
>                 value.set(string);
>                 if (value.isNone() || !value.isArray() ||
>                     value.type() != ControlTypeString ||
> @@ -327,7 +327,7 @@ protected:
>                         return TestFail;
>                 }
>  
> -               if (value.get<std::string>() != string) {
> +               if (value.get<std::string_view>() != string) {
>                         cerr << "Control value mismatch after setting to string" << endl;
>                         return TestFail;
>                 }
> diff --git a/utils/codegen/controls.py b/utils/codegen/controls.py
> index e51610481..9399727bd 100644
> --- a/utils/codegen/controls.py
> +++ b/utils/codegen/controls.py
> @@ -111,7 +111,7 @@ class Control(object):
>          size = self.__data.get('size')
>  
>          if typ == 'string':
> -            return 'std::string'
> +            return 'std::string_view'
>  
>          if self.__size is None:
>              return typ
> -- 
> 2.49.0
>
Barnabás Pőcze April 25, 2025, 8:18 a.m. UTC | #2
Hi


2025. 04. 23. 10:33 keltezéssel, Kieran Bingham írta:
> Quoting Barnabás Pőcze (2025-04-22 13:47:51)
>> When retrieving the value from a `ControlValue` usually one of two
>> things happen: a small, trivially copyable object is returned by
>> value; or a view into the internal buffer is provided. This is true
>> for everything except strings, which are returned in `std::string`,
>> incurring the overhead of string construction.
>>
>> To guarantee no potentially "expensive" copies, use `std::string_view`
>> pointing to the internal buffer to return the value. This is similar
>> to how other array-like types are returned with a `Span<>`.
>>
>> This is an API break, but its scope is limited to just `properties::Model`.
> 
> This breaks in CI at the build-history stage:
> 
> https://gitlab.freedesktop.org/camera/libcamera/-/jobs/75119329#L1034
> 
> I wonder, are we missing a dependency declaration somewhere that should
> have otherwise caused something to get rebuilt? All of the other builds
> seemed to have worked on that pipeline ?

Yes indeed, `controls.py` is changed, but that is not considered by
meson, so it does not regenerate the cpp files because neither the
template nor `gen-controls.py`, etc. has changed.

I am not sure how it could be best addressed, there is `depend_files` where
`controls.py` could be specified, but I don't think it's too convenient.

Or there is the `modulefinder` thing in the standard library:

   >>> os.getcwd()
   '/libcamera/utils/codegen'
   >>> import modulefinder
   >>> m = modulefinder.ModuleFinder()
   >>> m.run_script("./gen-controls.py")
   >>> m.modules["controls"]
   Module('controls', '/libcamera/utils/codegen/controls.py')

I am wondering if this might be used to generate a dependency file that ninja
can interpret. Although this approach is definitely more complex.


Regards,
Barnabás Pőcze

> 
>>
>> Bug: https://bugs.libcamera.org/show_bug.cgi?id=256
>> Signed-off-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com>
>> ---
>>   include/libcamera/control_ids.h.in  |  1 +
>>   include/libcamera/controls.h        | 15 ++++++++-------
>>   src/apps/cam/capture_script.cpp     |  2 +-
>>   src/apps/cam/main.cpp               |  2 +-
>>   src/apps/common/dng_writer.cpp      |  2 +-
>>   src/apps/qcam/cam_select_dialog.cpp |  6 +++---
>>   src/py/libcamera/py_helpers.cpp     |  4 ++--
>>   test/controls/control_value.cpp     |  4 ++--
>>   utils/codegen/controls.py           |  2 +-
>>   9 files changed, 20 insertions(+), 18 deletions(-)
>>
>> diff --git a/include/libcamera/control_ids.h.in b/include/libcamera/control_ids.h.in
>> index 5d0594c68..41997f79f 100644
>> --- a/include/libcamera/control_ids.h.in
>> +++ b/include/libcamera/control_ids.h.in
>> @@ -13,6 +13,7 @@
>>   #include <map>
>>   #include <stdint.h>
>>   #include <string>
>> +#include <string_view>
>>   
>>   #include <libcamera/controls.h>
>>   
>> diff --git a/include/libcamera/controls.h b/include/libcamera/controls.h
>> index 4bfe9615c..275f45200 100644
>> --- a/include/libcamera/controls.h
>> +++ b/include/libcamera/controls.h
>> @@ -13,6 +13,7 @@
>>   #include <set>
>>   #include <stdint.h>
>>   #include <string>
>> +#include <string_view>
>>   #include <unordered_map>
>>   #include <vector>
>>   
>> @@ -96,7 +97,7 @@ struct control_type<float> {
>>   };
>>   
>>   template<>
>> -struct control_type<std::string> {
>> +struct control_type<std::string_view> {
>>          static constexpr ControlType value = ControlTypeString;
>>          static constexpr std::size_t size = 0;
>>   };
>> @@ -138,7 +139,7 @@ public:
>>   #ifndef __DOXYGEN__
>>          template<typename T, std::enable_if_t<!details::is_span<T>::value &&
>>                                                details::control_type<T>::value &&
>> -                                             !std::is_same<std::string, std::remove_cv_t<T>>::value,
>> +                                             !std::is_same<std::string_view, std::remove_cv_t<T>>::value,
>>                                                std::nullptr_t> = nullptr>
>>          ControlValue(const T &value)
>>                  : type_(ControlTypeNone), numElements_(0)
>> @@ -148,7 +149,7 @@ public:
>>          }
>>   
>>          template<typename T, std::enable_if_t<details::is_span<T>::value ||
>> -                                             std::is_same<std::string, std::remove_cv_t<T>>::value,
>> +                                             std::is_same<std::string_view, std::remove_cv_t<T>>::value,
>>                                                std::nullptr_t> = nullptr>
>>   #else
>>          template<typename T>
>> @@ -182,7 +183,7 @@ public:
>>   
>>   #ifndef __DOXYGEN__
>>          template<typename T, std::enable_if_t<!details::is_span<T>::value &&
>> -                                             !std::is_same<std::string, std::remove_cv_t<T>>::value,
>> +                                             !std::is_same<std::string_view, std::remove_cv_t<T>>::value,
>>                                                std::nullptr_t> = nullptr>
>>          T get() const
>>          {
>> @@ -193,7 +194,7 @@ public:
>>          }
>>   
>>          template<typename T, std::enable_if_t<details::is_span<T>::value ||
>> -                                             std::is_same<std::string, std::remove_cv_t<T>>::value,
>> +                                             std::is_same<std::string_view, std::remove_cv_t<T>>::value,
>>                                                std::nullptr_t> = nullptr>
>>   #else
>>          template<typename T>
>> @@ -210,7 +211,7 @@ public:
>>   
>>   #ifndef __DOXYGEN__
>>          template<typename T, std::enable_if_t<!details::is_span<T>::value &&
>> -                                             !std::is_same<std::string, std::remove_cv_t<T>>::value,
>> +                                             !std::is_same<std::string_view, std::remove_cv_t<T>>::value,
>>                                                std::nullptr_t> = nullptr>
>>          void set(const T &value)
>>          {
>> @@ -219,7 +220,7 @@ public:
>>          }
>>   
>>          template<typename T, std::enable_if_t<details::is_span<T>::value ||
>> -                                             std::is_same<std::string, std::remove_cv_t<T>>::value,
>> +                                             std::is_same<std::string_view, std::remove_cv_t<T>>::value,
>>                                                std::nullptr_t> = nullptr>
>>   #else
>>          template<typename T>
>> diff --git a/src/apps/cam/capture_script.cpp b/src/apps/cam/capture_script.cpp
>> index fdf82efc0..bc4c7c3a0 100644
>> --- a/src/apps/cam/capture_script.cpp
>> +++ b/src/apps/cam/capture_script.cpp
>> @@ -502,7 +502,7 @@ ControlValue CaptureScript::parseScalarControl(const ControlId *id,
>>                  break;
>>          }
>>          case ControlTypeString: {
>> -               value.set<std::string>(repr);
>> +               value.set<std::string_view>(repr);
>>                  break;
>>          }
>>          default:
>> diff --git a/src/apps/cam/main.cpp b/src/apps/cam/main.cpp
>> index fa266eca6..157d238d7 100644
>> --- a/src/apps/cam/main.cpp
>> +++ b/src/apps/cam/main.cpp
>> @@ -337,7 +337,7 @@ std::string CamApp::cameraName(const Camera *camera)
>>                   */
>>                  const auto &model = props.get(properties::Model);
>>                  if (model)
>> -                       name = "'" + *model + "' ";
>> +                       name.append("'").append(*model).append("' ");
>>          }
>>   
>>          name += "(" + camera->id() + ")";
>> diff --git a/src/apps/common/dng_writer.cpp b/src/apps/common/dng_writer.cpp
>> index ac4619511..8d57023e1 100644
>> --- a/src/apps/common/dng_writer.cpp
>> +++ b/src/apps/common/dng_writer.cpp
>> @@ -564,7 +564,7 @@ int DNGWriter::write(const char *filename, const Camera *camera,
>>   
>>          const auto &model = cameraProperties.get(properties::Model);
>>          if (model) {
>> -               TIFFSetField(tif, TIFFTAG_MODEL, model->c_str());
>> +               TIFFSetField(tif, TIFFTAG_MODEL, std::string(*model).c_str());
>>                  /* \todo set TIFFTAG_UNIQUECAMERAMODEL. */
>>          }
>>   
>> diff --git a/src/apps/qcam/cam_select_dialog.cpp b/src/apps/qcam/cam_select_dialog.cpp
>> index 6b6d0713c..9f25ba091 100644
>> --- a/src/apps/qcam/cam_select_dialog.cpp
>> +++ b/src/apps/qcam/cam_select_dialog.cpp
>> @@ -114,8 +114,8 @@ void CameraSelectorDialog::updateCameraInfo(QString cameraId)
>>                  cameraLocation_->setText("Unknown");
>>          }
>>   
>> -       const auto &model = properties.get(libcamera::properties::Model)
>> -                                   .value_or("Unknown");
>> +       const auto model = properties.get(libcamera::properties::Model)
>> +                                    .value_or("Unknown");
>>   
>> -       cameraModel_->setText(QString::fromStdString(model));
>> +       cameraModel_->setText(QString::fromUtf8(model.data(), model.length()));
>>   }
>> diff --git a/src/py/libcamera/py_helpers.cpp b/src/py/libcamera/py_helpers.cpp
>> index 1ad1d4c1a..8c55ef845 100644
>> --- a/src/py/libcamera/py_helpers.cpp
>> +++ b/src/py/libcamera/py_helpers.cpp
>> @@ -47,7 +47,7 @@ py::object controlValueToPy(const ControlValue &cv)
>>          case ControlTypeFloat:
>>                  return valueOrTuple<float>(cv);
>>          case ControlTypeString:
>> -               return py::cast(cv.get<std::string>());
>> +               return py::cast(cv.get<std::string_view>());
>>          case ControlTypeSize: {
>>                  const Size *v = reinterpret_cast<const Size *>(cv.data().data());
>>                  return py::cast(v);
>> @@ -88,7 +88,7 @@ ControlValue pyToControlValue(const py::object &ob, ControlType type)
>>          case ControlTypeFloat:
>>                  return controlValueMaybeArray<float>(ob);
>>          case ControlTypeString:
>> -               return ControlValue(ob.cast<std::string>());
>> +               return ControlValue(ob.cast<std::string_view>());
>>          case ControlTypeRectangle:
>>                  return controlValueMaybeArray<Rectangle>(ob);
>>          case ControlTypeSize:
>> diff --git a/test/controls/control_value.cpp b/test/controls/control_value.cpp
>> index 5084fd0cf..032050a77 100644
>> --- a/test/controls/control_value.cpp
>> +++ b/test/controls/control_value.cpp
>> @@ -318,7 +318,7 @@ protected:
>>                  /*
>>                   * String type.
>>                   */
>> -               std::string string{ "libcamera" };
>> +               std::string_view string{ "libcamera" };
>>                  value.set(string);
>>                  if (value.isNone() || !value.isArray() ||
>>                      value.type() != ControlTypeString ||
>> @@ -327,7 +327,7 @@ protected:
>>                          return TestFail;
>>                  }
>>   
>> -               if (value.get<std::string>() != string) {
>> +               if (value.get<std::string_view>() != string) {
>>                          cerr << "Control value mismatch after setting to string" << endl;
>>                          return TestFail;
>>                  }
>> diff --git a/utils/codegen/controls.py b/utils/codegen/controls.py
>> index e51610481..9399727bd 100644
>> --- a/utils/codegen/controls.py
>> +++ b/utils/codegen/controls.py
>> @@ -111,7 +111,7 @@ class Control(object):
>>           size = self.__data.get('size')
>>   
>>           if typ == 'string':
>> -            return 'std::string'
>> +            return 'std::string_view'
>>   
>>           if self.__size is None:
>>               return typ
>> -- 
>> 2.49.0
>>
Kieran Bingham May 1, 2025, 10:16 a.m. UTC | #3
Quoting Barnabás Pőcze (2025-04-25 09:18:28)
> Hi
> 
> 
> 2025. 04. 23. 10:33 keltezéssel, Kieran Bingham írta:
> > Quoting Barnabás Pőcze (2025-04-22 13:47:51)
> >> When retrieving the value from a `ControlValue` usually one of two
> >> things happen: a small, trivially copyable object is returned by
> >> value; or a view into the internal buffer is provided. This is true
> >> for everything except strings, which are returned in `std::string`,
> >> incurring the overhead of string construction.
> >>
> >> To guarantee no potentially "expensive" copies, use `std::string_view`
> >> pointing to the internal buffer to return the value. This is similar
> >> to how other array-like types are returned with a `Span<>`.
> >>
> >> This is an API break, but its scope is limited to just `properties::Model`.
> > 
> > This breaks in CI at the build-history stage:
> > 
> > https://gitlab.freedesktop.org/camera/libcamera/-/jobs/75119329#L1034
> > 
> > I wonder, are we missing a dependency declaration somewhere that should
> > have otherwise caused something to get rebuilt? All of the other builds
> > seemed to have worked on that pipeline ?
> 
> Yes indeed, `controls.py` is changed, but that is not considered by
> meson, so it does not regenerate the cpp files because neither the
> template nor `gen-controls.py`, etc. has changed.

Yikes. Probably something to bring up in the meson channels for support.
I have no idea on a (generic/longer term) solution I'm afraid.

> I am not sure how it could be best addressed, there is `depend_files` where
> `controls.py` could be specified, but I don't think it's too convenient.
> 
> Or there is the `modulefinder` thing in the standard library:
> 
>    >>> os.getcwd()
>    '/libcamera/utils/codegen'
>    >>> import modulefinder
>    >>> m = modulefinder.ModuleFinder()
>    >>> m.run_script("./gen-controls.py")
>    >>> m.modules["controls"]
>    Module('controls', '/libcamera/utils/codegen/controls.py')
> 
> I am wondering if this might be used to generate a dependency file that ninja
> can interpret. Although this approach is definitely more complex.

Yes, I can see that could get ugly - and we're not supposed to have to
parse the dependencies of every tool we use. Ok - so I guess this one is
different because it's our own in built tooling ... 

I think we could :
 - Contact meson developers for support
 - Update this patch to 'also' (trivially) modify either the template or
   gen-controls.py
 - Ignore the build-history failure and just keep it as is..

I think if we want to land this series - at worst I'm ok with the last
option - but I'd rather keep it as a last resort to make sure we
maintain bisectability. And for that - I think even an arbitrary comment
update to the gen-controls.py which could be later removed would be fine
if it came to it.



> 
> 
> Regards,
> Barnabás Pőcze
> 
> > 
> >>
> >> Bug: https://bugs.libcamera.org/show_bug.cgi?id=256
> >> Signed-off-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com>
> >> ---
> >>   include/libcamera/control_ids.h.in  |  1 +
> >>   include/libcamera/controls.h        | 15 ++++++++-------
> >>   src/apps/cam/capture_script.cpp     |  2 +-
> >>   src/apps/cam/main.cpp               |  2 +-
> >>   src/apps/common/dng_writer.cpp      |  2 +-
> >>   src/apps/qcam/cam_select_dialog.cpp |  6 +++---
> >>   src/py/libcamera/py_helpers.cpp     |  4 ++--
> >>   test/controls/control_value.cpp     |  4 ++--
> >>   utils/codegen/controls.py           |  2 +-
> >>   9 files changed, 20 insertions(+), 18 deletions(-)
> >>
> >> diff --git a/include/libcamera/control_ids.h.in b/include/libcamera/control_ids.h.in
> >> index 5d0594c68..41997f79f 100644
> >> --- a/include/libcamera/control_ids.h.in
> >> +++ b/include/libcamera/control_ids.h.in
> >> @@ -13,6 +13,7 @@
> >>   #include <map>
> >>   #include <stdint.h>
> >>   #include <string>
> >> +#include <string_view>
> >>   
> >>   #include <libcamera/controls.h>
> >>   
> >> diff --git a/include/libcamera/controls.h b/include/libcamera/controls.h
> >> index 4bfe9615c..275f45200 100644
> >> --- a/include/libcamera/controls.h
> >> +++ b/include/libcamera/controls.h
> >> @@ -13,6 +13,7 @@
> >>   #include <set>
> >>   #include <stdint.h>
> >>   #include <string>
> >> +#include <string_view>
> >>   #include <unordered_map>
> >>   #include <vector>
> >>   
> >> @@ -96,7 +97,7 @@ struct control_type<float> {
> >>   };
> >>   
> >>   template<>
> >> -struct control_type<std::string> {
> >> +struct control_type<std::string_view> {
> >>          static constexpr ControlType value = ControlTypeString;
> >>          static constexpr std::size_t size = 0;
> >>   };
> >> @@ -138,7 +139,7 @@ public:
> >>   #ifndef __DOXYGEN__
> >>          template<typename T, std::enable_if_t<!details::is_span<T>::value &&
> >>                                                details::control_type<T>::value &&
> >> -                                             !std::is_same<std::string, std::remove_cv_t<T>>::value,
> >> +                                             !std::is_same<std::string_view, std::remove_cv_t<T>>::value,
> >>                                                std::nullptr_t> = nullptr>
> >>          ControlValue(const T &value)
> >>                  : type_(ControlTypeNone), numElements_(0)
> >> @@ -148,7 +149,7 @@ public:
> >>          }
> >>   
> >>          template<typename T, std::enable_if_t<details::is_span<T>::value ||
> >> -                                             std::is_same<std::string, std::remove_cv_t<T>>::value,
> >> +                                             std::is_same<std::string_view, std::remove_cv_t<T>>::value,
> >>                                                std::nullptr_t> = nullptr>
> >>   #else
> >>          template<typename T>
> >> @@ -182,7 +183,7 @@ public:
> >>   
> >>   #ifndef __DOXYGEN__
> >>          template<typename T, std::enable_if_t<!details::is_span<T>::value &&
> >> -                                             !std::is_same<std::string, std::remove_cv_t<T>>::value,
> >> +                                             !std::is_same<std::string_view, std::remove_cv_t<T>>::value,
> >>                                                std::nullptr_t> = nullptr>
> >>          T get() const
> >>          {
> >> @@ -193,7 +194,7 @@ public:
> >>          }
> >>   
> >>          template<typename T, std::enable_if_t<details::is_span<T>::value ||
> >> -                                             std::is_same<std::string, std::remove_cv_t<T>>::value,
> >> +                                             std::is_same<std::string_view, std::remove_cv_t<T>>::value,
> >>                                                std::nullptr_t> = nullptr>
> >>   #else
> >>          template<typename T>
> >> @@ -210,7 +211,7 @@ public:
> >>   
> >>   #ifndef __DOXYGEN__
> >>          template<typename T, std::enable_if_t<!details::is_span<T>::value &&
> >> -                                             !std::is_same<std::string, std::remove_cv_t<T>>::value,
> >> +                                             !std::is_same<std::string_view, std::remove_cv_t<T>>::value,
> >>                                                std::nullptr_t> = nullptr>
> >>          void set(const T &value)
> >>          {
> >> @@ -219,7 +220,7 @@ public:
> >>          }
> >>   
> >>          template<typename T, std::enable_if_t<details::is_span<T>::value ||
> >> -                                             std::is_same<std::string, std::remove_cv_t<T>>::value,
> >> +                                             std::is_same<std::string_view, std::remove_cv_t<T>>::value,
> >>                                                std::nullptr_t> = nullptr>
> >>   #else
> >>          template<typename T>
> >> diff --git a/src/apps/cam/capture_script.cpp b/src/apps/cam/capture_script.cpp
> >> index fdf82efc0..bc4c7c3a0 100644
> >> --- a/src/apps/cam/capture_script.cpp
> >> +++ b/src/apps/cam/capture_script.cpp
> >> @@ -502,7 +502,7 @@ ControlValue CaptureScript::parseScalarControl(const ControlId *id,
> >>                  break;
> >>          }
> >>          case ControlTypeString: {
> >> -               value.set<std::string>(repr);
> >> +               value.set<std::string_view>(repr);
> >>                  break;
> >>          }
> >>          default:
> >> diff --git a/src/apps/cam/main.cpp b/src/apps/cam/main.cpp
> >> index fa266eca6..157d238d7 100644
> >> --- a/src/apps/cam/main.cpp
> >> +++ b/src/apps/cam/main.cpp
> >> @@ -337,7 +337,7 @@ std::string CamApp::cameraName(const Camera *camera)
> >>                   */
> >>                  const auto &model = props.get(properties::Model);
> >>                  if (model)
> >> -                       name = "'" + *model + "' ";
> >> +                       name.append("'").append(*model).append("' ");

Aha, ok so this is the API breakage ... So we'll lose the ability to
flexibly represent strings ?

Does this append have to construct a string ? (i.e. are we going to end
up just 'moving' the new construction?

Any help/benefit from using include/libcamera/base/utils.h
libcamera::join();? Ah - no that would only add separateors - what we
would need as a string 'wrap' to allow wrapping the model with '
characters.

There are other occasions where I've thought about adding a wrap ..
wrapper function but it's probably overkill.



> >>          }
> >>   
> >>          name += "(" + camera->id() + ")";
> >> diff --git a/src/apps/common/dng_writer.cpp b/src/apps/common/dng_writer.cpp
> >> index ac4619511..8d57023e1 100644
> >> --- a/src/apps/common/dng_writer.cpp
> >> +++ b/src/apps/common/dng_writer.cpp
> >> @@ -564,7 +564,7 @@ int DNGWriter::write(const char *filename, const Camera *camera,
> >>   
> >>          const auto &model = cameraProperties.get(properties::Model);
> >>          if (model) {
> >> -               TIFFSetField(tif, TIFFTAG_MODEL, model->c_str());
> >> +               TIFFSetField(tif, TIFFTAG_MODEL, std::string(*model).c_str());
> >>                  /* \todo set TIFFTAG_UNIQUECAMERAMODEL. */

This looks like a new construction ... does this make a copy ?


As far as I can tell - this is functionally correct/valid - and all the
build tests (except the build-history) work - so I'd put :


Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>

Curious to see if this is something that provides enough 'benefit' over
changing how string controls are handled vs potential copies.

Do we ever expect strings to be expensive?

I could actually imagine some use cases where we could start making
internal/debug controls that might report larger strings in fact ... so
it might actaully be helpful there!



> >>          }
> >>   
> >> diff --git a/src/apps/qcam/cam_select_dialog.cpp b/src/apps/qcam/cam_select_dialog.cpp
> >> index 6b6d0713c..9f25ba091 100644
> >> --- a/src/apps/qcam/cam_select_dialog.cpp
> >> +++ b/src/apps/qcam/cam_select_dialog.cpp
> >> @@ -114,8 +114,8 @@ void CameraSelectorDialog::updateCameraInfo(QString cameraId)
> >>                  cameraLocation_->setText("Unknown");
> >>          }
> >>   
> >> -       const auto &model = properties.get(libcamera::properties::Model)
> >> -                                   .value_or("Unknown");
> >> +       const auto model = properties.get(libcamera::properties::Model)
> >> +                                    .value_or("Unknown");
> >>   
> >> -       cameraModel_->setText(QString::fromStdString(model));
> >> +       cameraModel_->setText(QString::fromUtf8(model.data(), model.length()));
> >>   }
> >> diff --git a/src/py/libcamera/py_helpers.cpp b/src/py/libcamera/py_helpers.cpp
> >> index 1ad1d4c1a..8c55ef845 100644
> >> --- a/src/py/libcamera/py_helpers.cpp
> >> +++ b/src/py/libcamera/py_helpers.cpp
> >> @@ -47,7 +47,7 @@ py::object controlValueToPy(const ControlValue &cv)
> >>          case ControlTypeFloat:
> >>                  return valueOrTuple<float>(cv);
> >>          case ControlTypeString:
> >> -               return py::cast(cv.get<std::string>());
> >> +               return py::cast(cv.get<std::string_view>());
> >>          case ControlTypeSize: {
> >>                  const Size *v = reinterpret_cast<const Size *>(cv.data().data());
> >>                  return py::cast(v);
> >> @@ -88,7 +88,7 @@ ControlValue pyToControlValue(const py::object &ob, ControlType type)
> >>          case ControlTypeFloat:
> >>                  return controlValueMaybeArray<float>(ob);
> >>          case ControlTypeString:
> >> -               return ControlValue(ob.cast<std::string>());
> >> +               return ControlValue(ob.cast<std::string_view>());
> >>          case ControlTypeRectangle:
> >>                  return controlValueMaybeArray<Rectangle>(ob);
> >>          case ControlTypeSize:
> >> diff --git a/test/controls/control_value.cpp b/test/controls/control_value.cpp
> >> index 5084fd0cf..032050a77 100644
> >> --- a/test/controls/control_value.cpp
> >> +++ b/test/controls/control_value.cpp
> >> @@ -318,7 +318,7 @@ protected:
> >>                  /*
> >>                   * String type.
> >>                   */
> >> -               std::string string{ "libcamera" };
> >> +               std::string_view string{ "libcamera" };
> >>                  value.set(string);
> >>                  if (value.isNone() || !value.isArray() ||
> >>                      value.type() != ControlTypeString ||
> >> @@ -327,7 +327,7 @@ protected:
> >>                          return TestFail;
> >>                  }
> >>   
> >> -               if (value.get<std::string>() != string) {
> >> +               if (value.get<std::string_view>() != string) {
> >>                          cerr << "Control value mismatch after setting to string" << endl;
> >>                          return TestFail;
> >>                  }
> >> diff --git a/utils/codegen/controls.py b/utils/codegen/controls.py
> >> index e51610481..9399727bd 100644
> >> --- a/utils/codegen/controls.py
> >> +++ b/utils/codegen/controls.py
> >> @@ -111,7 +111,7 @@ class Control(object):
> >>           size = self.__data.get('size')
> >>   
> >>           if typ == 'string':
> >> -            return 'std::string'
> >> +            return 'std::string_view'
> >>   
> >>           if self.__size is None:
> >>               return typ
> >> -- 
> >> 2.49.0
> >>
> @
Barnabás Pőcze May 1, 2025, 10:25 a.m. UTC | #4
2025. 05. 01. 12:16 keltezéssel, Kieran Bingham írta:
> Quoting Barnabás Pőcze (2025-04-25 09:18:28)
>> Hi
>>
>>
>> 2025. 04. 23. 10:33 keltezéssel, Kieran Bingham írta:
>>> Quoting Barnabás Pőcze (2025-04-22 13:47:51)
>>>> When retrieving the value from a `ControlValue` usually one of two
>>>> things happen: a small, trivially copyable object is returned by
>>>> value; or a view into the internal buffer is provided. This is true
>>>> for everything except strings, which are returned in `std::string`,
>>>> incurring the overhead of string construction.
>>>>
>>>> To guarantee no potentially "expensive" copies, use `std::string_view`
>>>> pointing to the internal buffer to return the value. This is similar
>>>> to how other array-like types are returned with a `Span<>`.
>>>>
>>>> This is an API break, but its scope is limited to just `properties::Model`.
>>>
>>> This breaks in CI at the build-history stage:
>>>
>>> https://gitlab.freedesktop.org/camera/libcamera/-/jobs/75119329#L1034
>>>
>>> I wonder, are we missing a dependency declaration somewhere that should
>>> have otherwise caused something to get rebuilt? All of the other builds
>>> seemed to have worked on that pipeline ?
>>
>> Yes indeed, `controls.py` is changed, but that is not considered by
>> meson, so it does not regenerate the cpp files because neither the
>> template nor `gen-controls.py`, etc. has changed.
> 
> Yikes. Probably something to bring up in the meson channels for support.
> I have no idea on a (generic/longer term) solution I'm afraid.
> 
>> I am not sure how it could be best addressed, there is `depend_files` where
>> `controls.py` could be specified, but I don't think it's too convenient.
>>
>> Or there is the `modulefinder` thing in the standard library:
>>
>>     >>> os.getcwd()
>>     '/libcamera/utils/codegen'
>>     >>> import modulefinder
>>     >>> m = modulefinder.ModuleFinder()
>>     >>> m.run_script("./gen-controls.py")
>>     >>> m.modules["controls"]
>>     Module('controls', '/libcamera/utils/codegen/controls.py')
>>
>> I am wondering if this might be used to generate a dependency file that ninja
>> can interpret. Although this approach is definitely more complex.
> 
> Yes, I can see that could get ugly - and we're not supposed to have to
> parse the dependencies of every tool we use. Ok - so I guess this one is
> different because it's our own in built tooling ...
> 
> I think we could :
>   - Contact meson developers for support
>   - Update this patch to 'also' (trivially) modify either the template or
>     gen-controls.py
>   - Ignore the build-history failure and just keep it as is..
> 
> I think if we want to land this series - at worst I'm ok with the last
> option - but I'd rather keep it as a last resort to make sure we
> maintain bisectability. And for that - I think even an arbitrary comment
> update to the gen-controls.py which could be later removed would be fine
> if it came to it.

v2 addresses this issue by adding `depend_files` to each `custom_target()`.
Not the best solution, but I couldn't find anything better.


> 
> 
> 
>>
>>
>> Regards,
>> Barnabás Pőcze
>>
>>>
>>>>
>>>> Bug: https://bugs.libcamera.org/show_bug.cgi?id=256
>>>> Signed-off-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com>
>>>> ---
>>>>    include/libcamera/control_ids.h.in  |  1 +
>>>>    include/libcamera/controls.h        | 15 ++++++++-------
>>>>    src/apps/cam/capture_script.cpp     |  2 +-
>>>>    src/apps/cam/main.cpp               |  2 +-
>>>>    src/apps/common/dng_writer.cpp      |  2 +-
>>>>    src/apps/qcam/cam_select_dialog.cpp |  6 +++---
>>>>    src/py/libcamera/py_helpers.cpp     |  4 ++--
>>>>    test/controls/control_value.cpp     |  4 ++--
>>>>    utils/codegen/controls.py           |  2 +-
>>>>    9 files changed, 20 insertions(+), 18 deletions(-)
>>>>
>>>> diff --git a/include/libcamera/control_ids.h.in b/include/libcamera/control_ids.h.in
>>>> index 5d0594c68..41997f79f 100644
>>>> --- a/include/libcamera/control_ids.h.in
>>>> +++ b/include/libcamera/control_ids.h.in
>>>> @@ -13,6 +13,7 @@
>>>>    #include <map>
>>>>    #include <stdint.h>
>>>>    #include <string>
>>>> +#include <string_view>
>>>>
>>>>    #include <libcamera/controls.h>
>>>>
>>>> diff --git a/include/libcamera/controls.h b/include/libcamera/controls.h
>>>> index 4bfe9615c..275f45200 100644
>>>> --- a/include/libcamera/controls.h
>>>> +++ b/include/libcamera/controls.h
>>>> @@ -13,6 +13,7 @@
>>>>    #include <set>
>>>>    #include <stdint.h>
>>>>    #include <string>
>>>> +#include <string_view>
>>>>    #include <unordered_map>
>>>>    #include <vector>
>>>>
>>>> @@ -96,7 +97,7 @@ struct control_type<float> {
>>>>    };
>>>>
>>>>    template<>
>>>> -struct control_type<std::string> {
>>>> +struct control_type<std::string_view> {
>>>>           static constexpr ControlType value = ControlTypeString;
>>>>           static constexpr std::size_t size = 0;
>>>>    };
>>>> @@ -138,7 +139,7 @@ public:
>>>>    #ifndef __DOXYGEN__
>>>>           template<typename T, std::enable_if_t<!details::is_span<T>::value &&
>>>>                                                 details::control_type<T>::value &&
>>>> -                                             !std::is_same<std::string, std::remove_cv_t<T>>::value,
>>>> +                                             !std::is_same<std::string_view, std::remove_cv_t<T>>::value,
>>>>                                                 std::nullptr_t> = nullptr>
>>>>           ControlValue(const T &value)
>>>>                   : type_(ControlTypeNone), numElements_(0)
>>>> @@ -148,7 +149,7 @@ public:
>>>>           }
>>>>
>>>>           template<typename T, std::enable_if_t<details::is_span<T>::value ||
>>>> -                                             std::is_same<std::string, std::remove_cv_t<T>>::value,
>>>> +                                             std::is_same<std::string_view, std::remove_cv_t<T>>::value,
>>>>                                                 std::nullptr_t> = nullptr>
>>>>    #else
>>>>           template<typename T>
>>>> @@ -182,7 +183,7 @@ public:
>>>>
>>>>    #ifndef __DOXYGEN__
>>>>           template<typename T, std::enable_if_t<!details::is_span<T>::value &&
>>>> -                                             !std::is_same<std::string, std::remove_cv_t<T>>::value,
>>>> +                                             !std::is_same<std::string_view, std::remove_cv_t<T>>::value,
>>>>                                                 std::nullptr_t> = nullptr>
>>>>           T get() const
>>>>           {
>>>> @@ -193,7 +194,7 @@ public:
>>>>           }
>>>>
>>>>           template<typename T, std::enable_if_t<details::is_span<T>::value ||
>>>> -                                             std::is_same<std::string, std::remove_cv_t<T>>::value,
>>>> +                                             std::is_same<std::string_view, std::remove_cv_t<T>>::value,
>>>>                                                 std::nullptr_t> = nullptr>
>>>>    #else
>>>>           template<typename T>
>>>> @@ -210,7 +211,7 @@ public:
>>>>
>>>>    #ifndef __DOXYGEN__
>>>>           template<typename T, std::enable_if_t<!details::is_span<T>::value &&
>>>> -                                             !std::is_same<std::string, std::remove_cv_t<T>>::value,
>>>> +                                             !std::is_same<std::string_view, std::remove_cv_t<T>>::value,
>>>>                                                 std::nullptr_t> = nullptr>
>>>>           void set(const T &value)
>>>>           {
>>>> @@ -219,7 +220,7 @@ public:
>>>>           }
>>>>
>>>>           template<typename T, std::enable_if_t<details::is_span<T>::value ||
>>>> -                                             std::is_same<std::string, std::remove_cv_t<T>>::value,
>>>> +                                             std::is_same<std::string_view, std::remove_cv_t<T>>::value,
>>>>                                                 std::nullptr_t> = nullptr>
>>>>    #else
>>>>           template<typename T>
>>>> diff --git a/src/apps/cam/capture_script.cpp b/src/apps/cam/capture_script.cpp
>>>> index fdf82efc0..bc4c7c3a0 100644
>>>> --- a/src/apps/cam/capture_script.cpp
>>>> +++ b/src/apps/cam/capture_script.cpp
>>>> @@ -502,7 +502,7 @@ ControlValue CaptureScript::parseScalarControl(const ControlId *id,
>>>>                   break;
>>>>           }
>>>>           case ControlTypeString: {
>>>> -               value.set<std::string>(repr);
>>>> +               value.set<std::string_view>(repr);
>>>>                   break;
>>>>           }
>>>>           default:
>>>> diff --git a/src/apps/cam/main.cpp b/src/apps/cam/main.cpp
>>>> index fa266eca6..157d238d7 100644
>>>> --- a/src/apps/cam/main.cpp
>>>> +++ b/src/apps/cam/main.cpp
>>>> @@ -337,7 +337,7 @@ std::string CamApp::cameraName(const Camera *camera)
>>>>                    */
>>>>                   const auto &model = props.get(properties::Model);
>>>>                   if (model)
>>>> -                       name = "'" + *model + "' ";
>>>> +                       name.append("'").append(*model).append("' ");
> 
> Aha, ok so this is the API breakage ... So we'll lose the ability to
> flexibly represent strings ?
> 
> Does this append have to construct a string ? (i.e. are we going to end
> up just 'moving' the new construction?
> 
> Any help/benefit from using include/libcamera/base/utils.h
> libcamera::join();? Ah - no that would only add separateors - what we
> would need as a string 'wrap' to allow wrapping the model with '
> characters.
> 
> There are other occasions where I've thought about adding a wrap ..
> wrapper function but it's probably overkill.
> 
> 
> 
>>>>           }
>>>>
>>>>           name += "(" + camera->id() + ")";
>>>> diff --git a/src/apps/common/dng_writer.cpp b/src/apps/common/dng_writer.cpp
>>>> index ac4619511..8d57023e1 100644
>>>> --- a/src/apps/common/dng_writer.cpp
>>>> +++ b/src/apps/common/dng_writer.cpp
>>>> @@ -564,7 +564,7 @@ int DNGWriter::write(const char *filename, const Camera *camera,
>>>>
>>>>           const auto &model = cameraProperties.get(properties::Model);
>>>>           if (model) {
>>>> -               TIFFSetField(tif, TIFFTAG_MODEL, model->c_str());
>>>> +               TIFFSetField(tif, TIFFTAG_MODEL, std::string(*model).c_str());
>>>>                   /* \todo set TIFFTAG_UNIQUECAMERAMODEL. */
> 
> This looks like a new construction ... does this make a copy ?
> 
> 
> As far as I can tell - this is functionally correct/valid - and all the
> build tests (except the build-history) work - so I'd put :
> 
> 
> Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
> 
> Curious to see if this is something that provides enough 'benefit' over
> changing how string controls are handled vs potential copies.
> 
> Do we ever expect strings to be expensive?
> 
> I could actually imagine some use cases where we could start making
> internal/debug controls that might report larger strings in fact ... so
> it might actaully be helpful there!
> 
> 
> 
>>>>           }
>>>>
>>>> diff --git a/src/apps/qcam/cam_select_dialog.cpp b/src/apps/qcam/cam_select_dialog.cpp
>>>> index 6b6d0713c..9f25ba091 100644
>>>> --- a/src/apps/qcam/cam_select_dialog.cpp
>>>> +++ b/src/apps/qcam/cam_select_dialog.cpp
>>>> @@ -114,8 +114,8 @@ void CameraSelectorDialog::updateCameraInfo(QString cameraId)
>>>>                   cameraLocation_->setText("Unknown");
>>>>           }
>>>>
>>>> -       const auto &model = properties.get(libcamera::properties::Model)
>>>> -                                   .value_or("Unknown");
>>>> +       const auto model = properties.get(libcamera::properties::Model)
>>>> +                                    .value_or("Unknown");
>>>>
>>>> -       cameraModel_->setText(QString::fromStdString(model));
>>>> +       cameraModel_->setText(QString::fromUtf8(model.data(), model.length()));
>>>>    }
>>>> diff --git a/src/py/libcamera/py_helpers.cpp b/src/py/libcamera/py_helpers.cpp
>>>> index 1ad1d4c1a..8c55ef845 100644
>>>> --- a/src/py/libcamera/py_helpers.cpp
>>>> +++ b/src/py/libcamera/py_helpers.cpp
>>>> @@ -47,7 +47,7 @@ py::object controlValueToPy(const ControlValue &cv)
>>>>           case ControlTypeFloat:
>>>>                   return valueOrTuple<float>(cv);
>>>>           case ControlTypeString:
>>>> -               return py::cast(cv.get<std::string>());
>>>> +               return py::cast(cv.get<std::string_view>());
>>>>           case ControlTypeSize: {
>>>>                   const Size *v = reinterpret_cast<const Size *>(cv.data().data());
>>>>                   return py::cast(v);
>>>> @@ -88,7 +88,7 @@ ControlValue pyToControlValue(const py::object &ob, ControlType type)
>>>>           case ControlTypeFloat:
>>>>                   return controlValueMaybeArray<float>(ob);
>>>>           case ControlTypeString:
>>>> -               return ControlValue(ob.cast<std::string>());
>>>> +               return ControlValue(ob.cast<std::string_view>());
>>>>           case ControlTypeRectangle:
>>>>                   return controlValueMaybeArray<Rectangle>(ob);
>>>>           case ControlTypeSize:
>>>> diff --git a/test/controls/control_value.cpp b/test/controls/control_value.cpp
>>>> index 5084fd0cf..032050a77 100644
>>>> --- a/test/controls/control_value.cpp
>>>> +++ b/test/controls/control_value.cpp
>>>> @@ -318,7 +318,7 @@ protected:
>>>>                   /*
>>>>                    * String type.
>>>>                    */
>>>> -               std::string string{ "libcamera" };
>>>> +               std::string_view string{ "libcamera" };
>>>>                   value.set(string);
>>>>                   if (value.isNone() || !value.isArray() ||
>>>>                       value.type() != ControlTypeString ||
>>>> @@ -327,7 +327,7 @@ protected:
>>>>                           return TestFail;
>>>>                   }
>>>>
>>>> -               if (value.get<std::string>() != string) {
>>>> +               if (value.get<std::string_view>() != string) {
>>>>                           cerr << "Control value mismatch after setting to string" << endl;
>>>>                           return TestFail;
>>>>                   }
>>>> diff --git a/utils/codegen/controls.py b/utils/codegen/controls.py
>>>> index e51610481..9399727bd 100644
>>>> --- a/utils/codegen/controls.py
>>>> +++ b/utils/codegen/controls.py
>>>> @@ -111,7 +111,7 @@ class Control(object):
>>>>            size = self.__data.get('size')
>>>>
>>>>            if typ == 'string':
>>>> -            return 'std::string'
>>>> +            return 'std::string_view'
>>>>
>>>>            if self.__size is None:
>>>>                return typ
>>>> --
>>>> 2.49.0
>>>>
>> @
Kieran Bingham May 1, 2025, 11:45 a.m. UTC | #5
Quoting Barnabás Pőcze (2025-05-01 11:25:12)
> 2025. 05. 01. 12:16 keltezéssel, Kieran Bingham írta:
> > Quoting Barnabás Pőcze (2025-04-25 09:18:28)
> >> Hi
> >>
> >>
> >> 2025. 04. 23. 10:33 keltezéssel, Kieran Bingham írta:
> >>> Quoting Barnabás Pőcze (2025-04-22 13:47:51)
> >>>> When retrieving the value from a `ControlValue` usually one of two
> >>>> things happen: a small, trivially copyable object is returned by
> >>>> value; or a view into the internal buffer is provided. This is true
> >>>> for everything except strings, which are returned in `std::string`,
> >>>> incurring the overhead of string construction.
> >>>>
> >>>> To guarantee no potentially "expensive" copies, use `std::string_view`
> >>>> pointing to the internal buffer to return the value. This is similar
> >>>> to how other array-like types are returned with a `Span<>`.
> >>>>
> >>>> This is an API break, but its scope is limited to just `properties::Model`.
> >>>
> >>> This breaks in CI at the build-history stage:
> >>>
> >>> https://gitlab.freedesktop.org/camera/libcamera/-/jobs/75119329#L1034
> >>>
> >>> I wonder, are we missing a dependency declaration somewhere that should
> >>> have otherwise caused something to get rebuilt? All of the other builds
> >>> seemed to have worked on that pipeline ?
> >>
> >> Yes indeed, `controls.py` is changed, but that is not considered by
> >> meson, so it does not regenerate the cpp files because neither the
> >> template nor `gen-controls.py`, etc. has changed.
> > 
> > Yikes. Probably something to bring up in the meson channels for support.
> > I have no idea on a (generic/longer term) solution I'm afraid.
> > 
> >> I am not sure how it could be best addressed, there is `depend_files` where
> >> `controls.py` could be specified, but I don't think it's too convenient.
> >>
> >> Or there is the `modulefinder` thing in the standard library:
> >>
> >>     >>> os.getcwd()
> >>     '/libcamera/utils/codegen'
> >>     >>> import modulefinder
> >>     >>> m = modulefinder.ModuleFinder()
> >>     >>> m.run_script("./gen-controls.py")
> >>     >>> m.modules["controls"]
> >>     Module('controls', '/libcamera/utils/codegen/controls.py')
> >>
> >> I am wondering if this might be used to generate a dependency file that ninja
> >> can interpret. Although this approach is definitely more complex.
> > 
> > Yes, I can see that could get ugly - and we're not supposed to have to
> > parse the dependencies of every tool we use. Ok - so I guess this one is
> > different because it's our own in built tooling ...
> > 
> > I think we could :
> >   - Contact meson developers for support
> >   - Update this patch to 'also' (trivially) modify either the template or
> >     gen-controls.py
> >   - Ignore the build-history failure and just keep it as is..
> > 
> > I think if we want to land this series - at worst I'm ok with the last
> > option - but I'd rather keep it as a last resort to make sure we
> > maintain bisectability. And for that - I think even an arbitrary comment
> > update to the gen-controls.py which could be later removed would be fine
> > if it came to it.
> 
> v2 addresses this issue by adding `depend_files` to each `custom_target()`.
> Not the best solution, but I couldn't find anything better.

Excellent, that's fine with me.

There were a few other statements/questions below here... any comment? -
I won't bother asking them on the new version ... you can keep/add my
tag to the new version.


> 
> 
> > 
> > 
> > 
> >>
> >>
> >> Regards,
> >> Barnabás Pőcze
> >>
> >>>
> >>>>
> >>>> Bug: https://bugs.libcamera.org/show_bug.cgi?id=256
> >>>> Signed-off-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com>
> >>>> ---
> >>>>    include/libcamera/control_ids.h.in  |  1 +
> >>>>    include/libcamera/controls.h        | 15 ++++++++-------
> >>>>    src/apps/cam/capture_script.cpp     |  2 +-
> >>>>    src/apps/cam/main.cpp               |  2 +-
> >>>>    src/apps/common/dng_writer.cpp      |  2 +-
> >>>>    src/apps/qcam/cam_select_dialog.cpp |  6 +++---
> >>>>    src/py/libcamera/py_helpers.cpp     |  4 ++--
> >>>>    test/controls/control_value.cpp     |  4 ++--
> >>>>    utils/codegen/controls.py           |  2 +-
> >>>>    9 files changed, 20 insertions(+), 18 deletions(-)
> >>>>
> >>>> diff --git a/include/libcamera/control_ids.h.in b/include/libcamera/control_ids.h.in
> >>>> index 5d0594c68..41997f79f 100644
> >>>> --- a/include/libcamera/control_ids.h.in
> >>>> +++ b/include/libcamera/control_ids.h.in
> >>>> @@ -13,6 +13,7 @@
> >>>>    #include <map>
> >>>>    #include <stdint.h>
> >>>>    #include <string>
> >>>> +#include <string_view>
> >>>>
> >>>>    #include <libcamera/controls.h>
> >>>>
> >>>> diff --git a/include/libcamera/controls.h b/include/libcamera/controls.h
> >>>> index 4bfe9615c..275f45200 100644
> >>>> --- a/include/libcamera/controls.h
> >>>> +++ b/include/libcamera/controls.h
> >>>> @@ -13,6 +13,7 @@
> >>>>    #include <set>
> >>>>    #include <stdint.h>
> >>>>    #include <string>
> >>>> +#include <string_view>
> >>>>    #include <unordered_map>
> >>>>    #include <vector>
> >>>>
> >>>> @@ -96,7 +97,7 @@ struct control_type<float> {
> >>>>    };
> >>>>
> >>>>    template<>
> >>>> -struct control_type<std::string> {
> >>>> +struct control_type<std::string_view> {
> >>>>           static constexpr ControlType value = ControlTypeString;
> >>>>           static constexpr std::size_t size = 0;
> >>>>    };
> >>>> @@ -138,7 +139,7 @@ public:
> >>>>    #ifndef __DOXYGEN__
> >>>>           template<typename T, std::enable_if_t<!details::is_span<T>::value &&
> >>>>                                                 details::control_type<T>::value &&
> >>>> -                                             !std::is_same<std::string, std::remove_cv_t<T>>::value,
> >>>> +                                             !std::is_same<std::string_view, std::remove_cv_t<T>>::value,
> >>>>                                                 std::nullptr_t> = nullptr>
> >>>>           ControlValue(const T &value)
> >>>>                   : type_(ControlTypeNone), numElements_(0)
> >>>> @@ -148,7 +149,7 @@ public:
> >>>>           }
> >>>>
> >>>>           template<typename T, std::enable_if_t<details::is_span<T>::value ||
> >>>> -                                             std::is_same<std::string, std::remove_cv_t<T>>::value,
> >>>> +                                             std::is_same<std::string_view, std::remove_cv_t<T>>::value,
> >>>>                                                 std::nullptr_t> = nullptr>
> >>>>    #else
> >>>>           template<typename T>
> >>>> @@ -182,7 +183,7 @@ public:
> >>>>
> >>>>    #ifndef __DOXYGEN__
> >>>>           template<typename T, std::enable_if_t<!details::is_span<T>::value &&
> >>>> -                                             !std::is_same<std::string, std::remove_cv_t<T>>::value,
> >>>> +                                             !std::is_same<std::string_view, std::remove_cv_t<T>>::value,
> >>>>                                                 std::nullptr_t> = nullptr>
> >>>>           T get() const
> >>>>           {
> >>>> @@ -193,7 +194,7 @@ public:
> >>>>           }
> >>>>
> >>>>           template<typename T, std::enable_if_t<details::is_span<T>::value ||
> >>>> -                                             std::is_same<std::string, std::remove_cv_t<T>>::value,
> >>>> +                                             std::is_same<std::string_view, std::remove_cv_t<T>>::value,
> >>>>                                                 std::nullptr_t> = nullptr>
> >>>>    #else
> >>>>           template<typename T>
> >>>> @@ -210,7 +211,7 @@ public:
> >>>>
> >>>>    #ifndef __DOXYGEN__
> >>>>           template<typename T, std::enable_if_t<!details::is_span<T>::value &&
> >>>> -                                             !std::is_same<std::string, std::remove_cv_t<T>>::value,
> >>>> +                                             !std::is_same<std::string_view, std::remove_cv_t<T>>::value,
> >>>>                                                 std::nullptr_t> = nullptr>
> >>>>           void set(const T &value)
> >>>>           {
> >>>> @@ -219,7 +220,7 @@ public:
> >>>>           }
> >>>>
> >>>>           template<typename T, std::enable_if_t<details::is_span<T>::value ||
> >>>> -                                             std::is_same<std::string, std::remove_cv_t<T>>::value,
> >>>> +                                             std::is_same<std::string_view, std::remove_cv_t<T>>::value,
> >>>>                                                 std::nullptr_t> = nullptr>
> >>>>    #else
> >>>>           template<typename T>
> >>>> diff --git a/src/apps/cam/capture_script.cpp b/src/apps/cam/capture_script.cpp
> >>>> index fdf82efc0..bc4c7c3a0 100644
> >>>> --- a/src/apps/cam/capture_script.cpp
> >>>> +++ b/src/apps/cam/capture_script.cpp
> >>>> @@ -502,7 +502,7 @@ ControlValue CaptureScript::parseScalarControl(const ControlId *id,
> >>>>                   break;
> >>>>           }
> >>>>           case ControlTypeString: {
> >>>> -               value.set<std::string>(repr);
> >>>> +               value.set<std::string_view>(repr);
> >>>>                   break;
> >>>>           }
> >>>>           default:
> >>>> diff --git a/src/apps/cam/main.cpp b/src/apps/cam/main.cpp
> >>>> index fa266eca6..157d238d7 100644
> >>>> --- a/src/apps/cam/main.cpp
> >>>> +++ b/src/apps/cam/main.cpp
> >>>> @@ -337,7 +337,7 @@ std::string CamApp::cameraName(const Camera *camera)
> >>>>                    */
> >>>>                   const auto &model = props.get(properties::Model);
> >>>>                   if (model)
> >>>> -                       name = "'" + *model + "' ";
> >>>> +                       name.append("'").append(*model).append("' ");
> > 
> > Aha, ok so this is the API breakage ... So we'll lose the ability to
> > flexibly represent strings ?
> > 
> > Does this append have to construct a string ? (i.e. are we going to end
> > up just 'moving' the new construction?
> > 
> > Any help/benefit from using include/libcamera/base/utils.h
> > libcamera::join();? Ah - no that would only add separateors - what we
> > would need as a string 'wrap' to allow wrapping the model with '
> > characters.
> > 
> > There are other occasions where I've thought about adding a wrap ..
> > wrapper function but it's probably overkill.
> > 
> > 
> > 
> >>>>           }
> >>>>
> >>>>           name += "(" + camera->id() + ")";
> >>>> diff --git a/src/apps/common/dng_writer.cpp b/src/apps/common/dng_writer.cpp
> >>>> index ac4619511..8d57023e1 100644
> >>>> --- a/src/apps/common/dng_writer.cpp
> >>>> +++ b/src/apps/common/dng_writer.cpp
> >>>> @@ -564,7 +564,7 @@ int DNGWriter::write(const char *filename, const Camera *camera,
> >>>>
> >>>>           const auto &model = cameraProperties.get(properties::Model);
> >>>>           if (model) {
> >>>> -               TIFFSetField(tif, TIFFTAG_MODEL, model->c_str());
> >>>> +               TIFFSetField(tif, TIFFTAG_MODEL, std::string(*model).c_str());
> >>>>                   /* \todo set TIFFTAG_UNIQUECAMERAMODEL. */
> > 
> > This looks like a new construction ... does this make a copy ?
> > 
> > 
> > As far as I can tell - this is functionally correct/valid - and all the
> > build tests (except the build-history) work - so I'd put :
> > 
> > 
> > Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
> > 
> > Curious to see if this is something that provides enough 'benefit' over
> > changing how string controls are handled vs potential copies.
> > 
> > Do we ever expect strings to be expensive?
> > 
> > I could actually imagine some use cases where we could start making
> > internal/debug controls that might report larger strings in fact ... so
> > it might actaully be helpful there!
> > 
> > 
> > 
> >>>>           }
> >>>>
> >>>> diff --git a/src/apps/qcam/cam_select_dialog.cpp b/src/apps/qcam/cam_select_dialog.cpp
> >>>> index 6b6d0713c..9f25ba091 100644
> >>>> --- a/src/apps/qcam/cam_select_dialog.cpp
> >>>> +++ b/src/apps/qcam/cam_select_dialog.cpp
> >>>> @@ -114,8 +114,8 @@ void CameraSelectorDialog::updateCameraInfo(QString cameraId)
> >>>>                   cameraLocation_->setText("Unknown");
> >>>>           }
> >>>>
> >>>> -       const auto &model = properties.get(libcamera::properties::Model)
> >>>> -                                   .value_or("Unknown");
> >>>> +       const auto model = properties.get(libcamera::properties::Model)
> >>>> +                                    .value_or("Unknown");
> >>>>
> >>>> -       cameraModel_->setText(QString::fromStdString(model));
> >>>> +       cameraModel_->setText(QString::fromUtf8(model.data(), model.length()));
> >>>>    }
> >>>> diff --git a/src/py/libcamera/py_helpers.cpp b/src/py/libcamera/py_helpers.cpp
> >>>> index 1ad1d4c1a..8c55ef845 100644
> >>>> --- a/src/py/libcamera/py_helpers.cpp
> >>>> +++ b/src/py/libcamera/py_helpers.cpp
> >>>> @@ -47,7 +47,7 @@ py::object controlValueToPy(const ControlValue &cv)
> >>>>           case ControlTypeFloat:
> >>>>                   return valueOrTuple<float>(cv);
> >>>>           case ControlTypeString:
> >>>> -               return py::cast(cv.get<std::string>());
> >>>> +               return py::cast(cv.get<std::string_view>());
> >>>>           case ControlTypeSize: {
> >>>>                   const Size *v = reinterpret_cast<const Size *>(cv.data().data());
> >>>>                   return py::cast(v);
> >>>> @@ -88,7 +88,7 @@ ControlValue pyToControlValue(const py::object &ob, ControlType type)
> >>>>           case ControlTypeFloat:
> >>>>                   return controlValueMaybeArray<float>(ob);
> >>>>           case ControlTypeString:
> >>>> -               return ControlValue(ob.cast<std::string>());
> >>>> +               return ControlValue(ob.cast<std::string_view>());
> >>>>           case ControlTypeRectangle:
> >>>>                   return controlValueMaybeArray<Rectangle>(ob);
> >>>>           case ControlTypeSize:
> >>>> diff --git a/test/controls/control_value.cpp b/test/controls/control_value.cpp
> >>>> index 5084fd0cf..032050a77 100644
> >>>> --- a/test/controls/control_value.cpp
> >>>> +++ b/test/controls/control_value.cpp
> >>>> @@ -318,7 +318,7 @@ protected:
> >>>>                   /*
> >>>>                    * String type.
> >>>>                    */
> >>>> -               std::string string{ "libcamera" };
> >>>> +               std::string_view string{ "libcamera" };
> >>>>                   value.set(string);
> >>>>                   if (value.isNone() || !value.isArray() ||
> >>>>                       value.type() != ControlTypeString ||
> >>>> @@ -327,7 +327,7 @@ protected:
> >>>>                           return TestFail;
> >>>>                   }
> >>>>
> >>>> -               if (value.get<std::string>() != string) {
> >>>> +               if (value.get<std::string_view>() != string) {
> >>>>                           cerr << "Control value mismatch after setting to string" << endl;
> >>>>                           return TestFail;
> >>>>                   }
> >>>> diff --git a/utils/codegen/controls.py b/utils/codegen/controls.py
> >>>> index e51610481..9399727bd 100644
> >>>> --- a/utils/codegen/controls.py
> >>>> +++ b/utils/codegen/controls.py
> >>>> @@ -111,7 +111,7 @@ class Control(object):
> >>>>            size = self.__data.get('size')
> >>>>
> >>>>            if typ == 'string':
> >>>> -            return 'std::string'
> >>>> +            return 'std::string_view'
> >>>>
> >>>>            if self.__size is None:
> >>>>                return typ
> >>>> --
> >>>> 2.49.0
> >>>>
> >> @
>
Barnabás Pőcze May 1, 2025, 2:12 p.m. UTC | #6
2025. 05. 01. 13:45 keltezéssel, Kieran Bingham írta:
> Quoting Barnabás Pőcze (2025-05-01 11:25:12)
>> 2025. 05. 01. 12:16 keltezéssel, Kieran Bingham írta:
>>> Quoting Barnabás Pőcze (2025-04-25 09:18:28)
>>>> Hi
>>>>
>>>>
>>>> 2025. 04. 23. 10:33 keltezéssel, Kieran Bingham írta:
>>>>> Quoting Barnabás Pőcze (2025-04-22 13:47:51)
>>>>>> When retrieving the value from a `ControlValue` usually one of two
>>>>>> things happen: a small, trivially copyable object is returned by
>>>>>> value; or a view into the internal buffer is provided. This is true
>>>>>> for everything except strings, which are returned in `std::string`,
>>>>>> incurring the overhead of string construction.
>>>>>>
>>>>>> To guarantee no potentially "expensive" copies, use `std::string_view`
>>>>>> pointing to the internal buffer to return the value. This is similar
>>>>>> to how other array-like types are returned with a `Span<>`.
>>>>>>
>>>>>> This is an API break, but its scope is limited to just `properties::Model`.
>>>>>
>>>>> This breaks in CI at the build-history stage:
>>>>>
>>>>> https://gitlab.freedesktop.org/camera/libcamera/-/jobs/75119329#L1034
>>>>>
>>>>> I wonder, are we missing a dependency declaration somewhere that should
>>>>> have otherwise caused something to get rebuilt? All of the other builds
>>>>> seemed to have worked on that pipeline ?
>>>>
>>>> Yes indeed, `controls.py` is changed, but that is not considered by
>>>> meson, so it does not regenerate the cpp files because neither the
>>>> template nor `gen-controls.py`, etc. has changed.
>>>
>>> Yikes. Probably something to bring up in the meson channels for support.
>>> I have no idea on a (generic/longer term) solution I'm afraid.
>>>
>>>> I am not sure how it could be best addressed, there is `depend_files` where
>>>> `controls.py` could be specified, but I don't think it's too convenient.
>>>>
>>>> Or there is the `modulefinder` thing in the standard library:
>>>>
>>>>      >>> os.getcwd()
>>>>      '/libcamera/utils/codegen'
>>>>      >>> import modulefinder
>>>>      >>> m = modulefinder.ModuleFinder()
>>>>      >>> m.run_script("./gen-controls.py")
>>>>      >>> m.modules["controls"]
>>>>      Module('controls', '/libcamera/utils/codegen/controls.py')
>>>>
>>>> I am wondering if this might be used to generate a dependency file that ninja
>>>> can interpret. Although this approach is definitely more complex.
>>>
>>> Yes, I can see that could get ugly - and we're not supposed to have to
>>> parse the dependencies of every tool we use. Ok - so I guess this one is
>>> different because it's our own in built tooling ...
>>>
>>> I think we could :
>>>    - Contact meson developers for support
>>>    - Update this patch to 'also' (trivially) modify either the template or
>>>      gen-controls.py
>>>    - Ignore the build-history failure and just keep it as is..
>>>
>>> I think if we want to land this series - at worst I'm ok with the last
>>> option - but I'd rather keep it as a last resort to make sure we
>>> maintain bisectability. And for that - I think even an arbitrary comment
>>> update to the gen-controls.py which could be later removed would be fine
>>> if it came to it.
>>
>> v2 addresses this issue by adding `depend_files` to each `custom_target()`.
>> Not the best solution, but I couldn't find anything better.
> 
> Excellent, that's fine with me.
> 
> There were a few other statements/questions below here... any comment? -
> I won't bother asking them on the new version ... you can keep/add my
> tag to the new version.

Oops, I did miss those.


> 
> 
>>
>>
>>>
>>>
>>>
>>>>
>>>>
>>>> Regards,
>>>> Barnabás Pőcze
>>>>
>>>>>
>>>>>>
>>>>>> Bug: https://bugs.libcamera.org/show_bug.cgi?id=256
>>>>>> Signed-off-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com>
>>>>>> ---
>>>>>>     include/libcamera/control_ids.h.in  |  1 +
>>>>>>     include/libcamera/controls.h        | 15 ++++++++-------
>>>>>>     src/apps/cam/capture_script.cpp     |  2 +-
>>>>>>     src/apps/cam/main.cpp               |  2 +-
>>>>>>     src/apps/common/dng_writer.cpp      |  2 +-
>>>>>>     src/apps/qcam/cam_select_dialog.cpp |  6 +++---
>>>>>>     src/py/libcamera/py_helpers.cpp     |  4 ++--
>>>>>>     test/controls/control_value.cpp     |  4 ++--
>>>>>>     utils/codegen/controls.py           |  2 +-
>>>>>>     9 files changed, 20 insertions(+), 18 deletions(-)
>>>>>>
>>>>>> diff --git a/include/libcamera/control_ids.h.in b/include/libcamera/control_ids.h.in
>>>>>> index 5d0594c68..41997f79f 100644
>>>>>> --- a/include/libcamera/control_ids.h.in
>>>>>> +++ b/include/libcamera/control_ids.h.in
>>>>>> @@ -13,6 +13,7 @@
>>>>>>     #include <map>
>>>>>>     #include <stdint.h>
>>>>>>     #include <string>
>>>>>> +#include <string_view>
>>>>>>
>>>>>>     #include <libcamera/controls.h>
>>>>>>
>>>>>> diff --git a/include/libcamera/controls.h b/include/libcamera/controls.h
>>>>>> index 4bfe9615c..275f45200 100644
>>>>>> --- a/include/libcamera/controls.h
>>>>>> +++ b/include/libcamera/controls.h
>>>>>> @@ -13,6 +13,7 @@
>>>>>>     #include <set>
>>>>>>     #include <stdint.h>
>>>>>>     #include <string>
>>>>>> +#include <string_view>
>>>>>>     #include <unordered_map>
>>>>>>     #include <vector>
>>>>>>
>>>>>> @@ -96,7 +97,7 @@ struct control_type<float> {
>>>>>>     };
>>>>>>
>>>>>>     template<>
>>>>>> -struct control_type<std::string> {
>>>>>> +struct control_type<std::string_view> {
>>>>>>            static constexpr ControlType value = ControlTypeString;
>>>>>>            static constexpr std::size_t size = 0;
>>>>>>     };
>>>>>> @@ -138,7 +139,7 @@ public:
>>>>>>     #ifndef __DOXYGEN__
>>>>>>            template<typename T, std::enable_if_t<!details::is_span<T>::value &&
>>>>>>                                                  details::control_type<T>::value &&
>>>>>> -                                             !std::is_same<std::string, std::remove_cv_t<T>>::value,
>>>>>> +                                             !std::is_same<std::string_view, std::remove_cv_t<T>>::value,
>>>>>>                                                  std::nullptr_t> = nullptr>
>>>>>>            ControlValue(const T &value)
>>>>>>                    : type_(ControlTypeNone), numElements_(0)
>>>>>> @@ -148,7 +149,7 @@ public:
>>>>>>            }
>>>>>>
>>>>>>            template<typename T, std::enable_if_t<details::is_span<T>::value ||
>>>>>> -                                             std::is_same<std::string, std::remove_cv_t<T>>::value,
>>>>>> +                                             std::is_same<std::string_view, std::remove_cv_t<T>>::value,
>>>>>>                                                  std::nullptr_t> = nullptr>
>>>>>>     #else
>>>>>>            template<typename T>
>>>>>> @@ -182,7 +183,7 @@ public:
>>>>>>
>>>>>>     #ifndef __DOXYGEN__
>>>>>>            template<typename T, std::enable_if_t<!details::is_span<T>::value &&
>>>>>> -                                             !std::is_same<std::string, std::remove_cv_t<T>>::value,
>>>>>> +                                             !std::is_same<std::string_view, std::remove_cv_t<T>>::value,
>>>>>>                                                  std::nullptr_t> = nullptr>
>>>>>>            T get() const
>>>>>>            {
>>>>>> @@ -193,7 +194,7 @@ public:
>>>>>>            }
>>>>>>
>>>>>>            template<typename T, std::enable_if_t<details::is_span<T>::value ||
>>>>>> -                                             std::is_same<std::string, std::remove_cv_t<T>>::value,
>>>>>> +                                             std::is_same<std::string_view, std::remove_cv_t<T>>::value,
>>>>>>                                                  std::nullptr_t> = nullptr>
>>>>>>     #else
>>>>>>            template<typename T>
>>>>>> @@ -210,7 +211,7 @@ public:
>>>>>>
>>>>>>     #ifndef __DOXYGEN__
>>>>>>            template<typename T, std::enable_if_t<!details::is_span<T>::value &&
>>>>>> -                                             !std::is_same<std::string, std::remove_cv_t<T>>::value,
>>>>>> +                                             !std::is_same<std::string_view, std::remove_cv_t<T>>::value,
>>>>>>                                                  std::nullptr_t> = nullptr>
>>>>>>            void set(const T &value)
>>>>>>            {
>>>>>> @@ -219,7 +220,7 @@ public:
>>>>>>            }
>>>>>>
>>>>>>            template<typename T, std::enable_if_t<details::is_span<T>::value ||
>>>>>> -                                             std::is_same<std::string, std::remove_cv_t<T>>::value,
>>>>>> +                                             std::is_same<std::string_view, std::remove_cv_t<T>>::value,
>>>>>>                                                  std::nullptr_t> = nullptr>
>>>>>>     #else
>>>>>>            template<typename T>
>>>>>> diff --git a/src/apps/cam/capture_script.cpp b/src/apps/cam/capture_script.cpp
>>>>>> index fdf82efc0..bc4c7c3a0 100644
>>>>>> --- a/src/apps/cam/capture_script.cpp
>>>>>> +++ b/src/apps/cam/capture_script.cpp
>>>>>> @@ -502,7 +502,7 @@ ControlValue CaptureScript::parseScalarControl(const ControlId *id,
>>>>>>                    break;
>>>>>>            }
>>>>>>            case ControlTypeString: {
>>>>>> -               value.set<std::string>(repr);
>>>>>> +               value.set<std::string_view>(repr);
>>>>>>                    break;
>>>>>>            }
>>>>>>            default:
>>>>>> diff --git a/src/apps/cam/main.cpp b/src/apps/cam/main.cpp
>>>>>> index fa266eca6..157d238d7 100644
>>>>>> --- a/src/apps/cam/main.cpp
>>>>>> +++ b/src/apps/cam/main.cpp
>>>>>> @@ -337,7 +337,7 @@ std::string CamApp::cameraName(const Camera *camera)
>>>>>>                     */
>>>>>>                    const auto &model = props.get(properties::Model);
>>>>>>                    if (model)
>>>>>> -                       name = "'" + *model + "' ";
>>>>>> +                       name.append("'").append(*model).append("' ");
>>>
>>> Aha, ok so this is the API breakage ... So we'll lose the ability to
>>> flexibly represent strings ?

One can always do e.g. `std::string(x.get(controls::Model))`. By returning an
`std::string_view`, no `std::string` construction is forced. If the user wants
an `std::string`, then they can construct one as seen above, and they get the
previous behaviour back.


>>>
>>> Does this append have to construct a string ? (i.e. are we going to end
>>> up just 'moving' the new construction?

`std::string::append()` does not need to construct a separate string from `*model`.


>>>
>>> Any help/benefit from using include/libcamera/base/utils.h
>>> libcamera::join();? Ah - no that would only add separateors - what we
>>> would need as a string 'wrap' to allow wrapping the model with '
>>> characters.

FYI, there is `std::quoted()` when working with streams: https://en.cppreference.com/w/cpp/io/manip/quoted
Of course, that's not applicable here, but nonetheless I don't think
this is a big issue.



>>>
>>> There are other occasions where I've thought about adding a wrap ..
>>> wrapper function but it's probably overkill.
>>>
>>>
>>>
>>>>>>            }
>>>>>>
>>>>>>            name += "(" + camera->id() + ")";
>>>>>> diff --git a/src/apps/common/dng_writer.cpp b/src/apps/common/dng_writer.cpp
>>>>>> index ac4619511..8d57023e1 100644
>>>>>> --- a/src/apps/common/dng_writer.cpp
>>>>>> +++ b/src/apps/common/dng_writer.cpp
>>>>>> @@ -564,7 +564,7 @@ int DNGWriter::write(const char *filename, const Camera *camera,
>>>>>>
>>>>>>            const auto &model = cameraProperties.get(properties::Model);
>>>>>>            if (model) {
>>>>>> -               TIFFSetField(tif, TIFFTAG_MODEL, model->c_str());
>>>>>> +               TIFFSetField(tif, TIFFTAG_MODEL, std::string(*model).c_str());
>>>>>>                    /* \todo set TIFFTAG_UNIQUECAMERAMODEL. */
>>>
>>> This looks like a new construction ... does this make a copy ?

Yes, it does. Previously `cameraProperties.get(properties::Model)` would have
made the equivalent copy, but since that now returns an `std::string_view`,
the copy has to be explicitly made in the caller if an `std::string` is needed.
So the number of copies does not change. (I did not find a way to pass a (ptr, len)
pair to `TIFFSetField()` or similar, which could avoid the copy altogether.)


>>>
>>>
>>> As far as I can tell - this is functionally correct/valid - and all the
>>> build tests (except the build-history) work - so I'd put :
>>>
>>>
>>> Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
>>>
>>> Curious to see if this is something that provides enough 'benefit' over
>>> changing how string controls are handled vs potential copies.
>>>
>>> Do we ever expect strings to be expensive?

To be honest I don't really see it ever becoming a bottleneck.


Regards,
Barnabás Pőcze


>>>
>>> I could actually imagine some use cases where we could start making
>>> internal/debug controls that might report larger strings in fact ... so
>>> it might actaully be helpful there!
>>>
>>>
>>>
>>>>>>            }
>>>>>>
>>>>>> diff --git a/src/apps/qcam/cam_select_dialog.cpp b/src/apps/qcam/cam_select_dialog.cpp
>>>>>> index 6b6d0713c..9f25ba091 100644
>>>>>> --- a/src/apps/qcam/cam_select_dialog.cpp
>>>>>> +++ b/src/apps/qcam/cam_select_dialog.cpp
>>>>>> @@ -114,8 +114,8 @@ void CameraSelectorDialog::updateCameraInfo(QString cameraId)
>>>>>>                    cameraLocation_->setText("Unknown");
>>>>>>            }
>>>>>>
>>>>>> -       const auto &model = properties.get(libcamera::properties::Model)
>>>>>> -                                   .value_or("Unknown");
>>>>>> +       const auto model = properties.get(libcamera::properties::Model)
>>>>>> +                                    .value_or("Unknown");
>>>>>>
>>>>>> -       cameraModel_->setText(QString::fromStdString(model));
>>>>>> +       cameraModel_->setText(QString::fromUtf8(model.data(), model.length()));
>>>>>>     }
>>>>>> diff --git a/src/py/libcamera/py_helpers.cpp b/src/py/libcamera/py_helpers.cpp
>>>>>> index 1ad1d4c1a..8c55ef845 100644
>>>>>> --- a/src/py/libcamera/py_helpers.cpp
>>>>>> +++ b/src/py/libcamera/py_helpers.cpp
>>>>>> @@ -47,7 +47,7 @@ py::object controlValueToPy(const ControlValue &cv)
>>>>>>            case ControlTypeFloat:
>>>>>>                    return valueOrTuple<float>(cv);
>>>>>>            case ControlTypeString:
>>>>>> -               return py::cast(cv.get<std::string>());
>>>>>> +               return py::cast(cv.get<std::string_view>());
>>>>>>            case ControlTypeSize: {
>>>>>>                    const Size *v = reinterpret_cast<const Size *>(cv.data().data());
>>>>>>                    return py::cast(v);
>>>>>> @@ -88,7 +88,7 @@ ControlValue pyToControlValue(const py::object &ob, ControlType type)
>>>>>>            case ControlTypeFloat:
>>>>>>                    return controlValueMaybeArray<float>(ob);
>>>>>>            case ControlTypeString:
>>>>>> -               return ControlValue(ob.cast<std::string>());
>>>>>> +               return ControlValue(ob.cast<std::string_view>());
>>>>>>            case ControlTypeRectangle:
>>>>>>                    return controlValueMaybeArray<Rectangle>(ob);
>>>>>>            case ControlTypeSize:
>>>>>> diff --git a/test/controls/control_value.cpp b/test/controls/control_value.cpp
>>>>>> index 5084fd0cf..032050a77 100644
>>>>>> --- a/test/controls/control_value.cpp
>>>>>> +++ b/test/controls/control_value.cpp
>>>>>> @@ -318,7 +318,7 @@ protected:
>>>>>>                    /*
>>>>>>                     * String type.
>>>>>>                     */
>>>>>> -               std::string string{ "libcamera" };
>>>>>> +               std::string_view string{ "libcamera" };
>>>>>>                    value.set(string);
>>>>>>                    if (value.isNone() || !value.isArray() ||
>>>>>>                        value.type() != ControlTypeString ||
>>>>>> @@ -327,7 +327,7 @@ protected:
>>>>>>                            return TestFail;
>>>>>>                    }
>>>>>>
>>>>>> -               if (value.get<std::string>() != string) {
>>>>>> +               if (value.get<std::string_view>() != string) {
>>>>>>                            cerr << "Control value mismatch after setting to string" << endl;
>>>>>>                            return TestFail;
>>>>>>                    }
>>>>>> diff --git a/utils/codegen/controls.py b/utils/codegen/controls.py
>>>>>> index e51610481..9399727bd 100644
>>>>>> --- a/utils/codegen/controls.py
>>>>>> +++ b/utils/codegen/controls.py
>>>>>> @@ -111,7 +111,7 @@ class Control(object):
>>>>>>             size = self.__data.get('size')
>>>>>>
>>>>>>             if typ == 'string':
>>>>>> -            return 'std::string'
>>>>>> +            return 'std::string_view'
>>>>>>
>>>>>>             if self.__size is None:
>>>>>>                 return typ
>>>>>> --
>>>>>> 2.49.0
>>>>>>
>>>> @
>>

Patch
diff mbox series

diff --git a/include/libcamera/control_ids.h.in b/include/libcamera/control_ids.h.in
index 5d0594c68..41997f79f 100644
--- a/include/libcamera/control_ids.h.in
+++ b/include/libcamera/control_ids.h.in
@@ -13,6 +13,7 @@ 
 #include <map>
 #include <stdint.h>
 #include <string>
+#include <string_view>
 
 #include <libcamera/controls.h>
 
diff --git a/include/libcamera/controls.h b/include/libcamera/controls.h
index 4bfe9615c..275f45200 100644
--- a/include/libcamera/controls.h
+++ b/include/libcamera/controls.h
@@ -13,6 +13,7 @@ 
 #include <set>
 #include <stdint.h>
 #include <string>
+#include <string_view>
 #include <unordered_map>
 #include <vector>
 
@@ -96,7 +97,7 @@  struct control_type<float> {
 };
 
 template<>
-struct control_type<std::string> {
+struct control_type<std::string_view> {
 	static constexpr ControlType value = ControlTypeString;
 	static constexpr std::size_t size = 0;
 };
@@ -138,7 +139,7 @@  public:
 #ifndef __DOXYGEN__
 	template<typename T, std::enable_if_t<!details::is_span<T>::value &&
 					      details::control_type<T>::value &&
-					      !std::is_same<std::string, std::remove_cv_t<T>>::value,
+					      !std::is_same<std::string_view, std::remove_cv_t<T>>::value,
 					      std::nullptr_t> = nullptr>
 	ControlValue(const T &value)
 		: type_(ControlTypeNone), numElements_(0)
@@ -148,7 +149,7 @@  public:
 	}
 
 	template<typename T, std::enable_if_t<details::is_span<T>::value ||
-					      std::is_same<std::string, std::remove_cv_t<T>>::value,
+					      std::is_same<std::string_view, std::remove_cv_t<T>>::value,
 					      std::nullptr_t> = nullptr>
 #else
 	template<typename T>
@@ -182,7 +183,7 @@  public:
 
 #ifndef __DOXYGEN__
 	template<typename T, std::enable_if_t<!details::is_span<T>::value &&
-					      !std::is_same<std::string, std::remove_cv_t<T>>::value,
+					      !std::is_same<std::string_view, std::remove_cv_t<T>>::value,
 					      std::nullptr_t> = nullptr>
 	T get() const
 	{
@@ -193,7 +194,7 @@  public:
 	}
 
 	template<typename T, std::enable_if_t<details::is_span<T>::value ||
-					      std::is_same<std::string, std::remove_cv_t<T>>::value,
+					      std::is_same<std::string_view, std::remove_cv_t<T>>::value,
 					      std::nullptr_t> = nullptr>
 #else
 	template<typename T>
@@ -210,7 +211,7 @@  public:
 
 #ifndef __DOXYGEN__
 	template<typename T, std::enable_if_t<!details::is_span<T>::value &&
-					      !std::is_same<std::string, std::remove_cv_t<T>>::value,
+					      !std::is_same<std::string_view, std::remove_cv_t<T>>::value,
 					      std::nullptr_t> = nullptr>
 	void set(const T &value)
 	{
@@ -219,7 +220,7 @@  public:
 	}
 
 	template<typename T, std::enable_if_t<details::is_span<T>::value ||
-					      std::is_same<std::string, std::remove_cv_t<T>>::value,
+					      std::is_same<std::string_view, std::remove_cv_t<T>>::value,
 					      std::nullptr_t> = nullptr>
 #else
 	template<typename T>
diff --git a/src/apps/cam/capture_script.cpp b/src/apps/cam/capture_script.cpp
index fdf82efc0..bc4c7c3a0 100644
--- a/src/apps/cam/capture_script.cpp
+++ b/src/apps/cam/capture_script.cpp
@@ -502,7 +502,7 @@  ControlValue CaptureScript::parseScalarControl(const ControlId *id,
 		break;
 	}
 	case ControlTypeString: {
-		value.set<std::string>(repr);
+		value.set<std::string_view>(repr);
 		break;
 	}
 	default:
diff --git a/src/apps/cam/main.cpp b/src/apps/cam/main.cpp
index fa266eca6..157d238d7 100644
--- a/src/apps/cam/main.cpp
+++ b/src/apps/cam/main.cpp
@@ -337,7 +337,7 @@  std::string CamApp::cameraName(const Camera *camera)
 		 */
 		const auto &model = props.get(properties::Model);
 		if (model)
-			name = "'" + *model + "' ";
+			name.append("'").append(*model).append("' ");
 	}
 
 	name += "(" + camera->id() + ")";
diff --git a/src/apps/common/dng_writer.cpp b/src/apps/common/dng_writer.cpp
index ac4619511..8d57023e1 100644
--- a/src/apps/common/dng_writer.cpp
+++ b/src/apps/common/dng_writer.cpp
@@ -564,7 +564,7 @@  int DNGWriter::write(const char *filename, const Camera *camera,
 
 	const auto &model = cameraProperties.get(properties::Model);
 	if (model) {
-		TIFFSetField(tif, TIFFTAG_MODEL, model->c_str());
+		TIFFSetField(tif, TIFFTAG_MODEL, std::string(*model).c_str());
 		/* \todo set TIFFTAG_UNIQUECAMERAMODEL. */
 	}
 
diff --git a/src/apps/qcam/cam_select_dialog.cpp b/src/apps/qcam/cam_select_dialog.cpp
index 6b6d0713c..9f25ba091 100644
--- a/src/apps/qcam/cam_select_dialog.cpp
+++ b/src/apps/qcam/cam_select_dialog.cpp
@@ -114,8 +114,8 @@  void CameraSelectorDialog::updateCameraInfo(QString cameraId)
 		cameraLocation_->setText("Unknown");
 	}
 
-	const auto &model = properties.get(libcamera::properties::Model)
-				    .value_or("Unknown");
+	const auto model = properties.get(libcamera::properties::Model)
+				     .value_or("Unknown");
 
-	cameraModel_->setText(QString::fromStdString(model));
+	cameraModel_->setText(QString::fromUtf8(model.data(), model.length()));
 }
diff --git a/src/py/libcamera/py_helpers.cpp b/src/py/libcamera/py_helpers.cpp
index 1ad1d4c1a..8c55ef845 100644
--- a/src/py/libcamera/py_helpers.cpp
+++ b/src/py/libcamera/py_helpers.cpp
@@ -47,7 +47,7 @@  py::object controlValueToPy(const ControlValue &cv)
 	case ControlTypeFloat:
 		return valueOrTuple<float>(cv);
 	case ControlTypeString:
-		return py::cast(cv.get<std::string>());
+		return py::cast(cv.get<std::string_view>());
 	case ControlTypeSize: {
 		const Size *v = reinterpret_cast<const Size *>(cv.data().data());
 		return py::cast(v);
@@ -88,7 +88,7 @@  ControlValue pyToControlValue(const py::object &ob, ControlType type)
 	case ControlTypeFloat:
 		return controlValueMaybeArray<float>(ob);
 	case ControlTypeString:
-		return ControlValue(ob.cast<std::string>());
+		return ControlValue(ob.cast<std::string_view>());
 	case ControlTypeRectangle:
 		return controlValueMaybeArray<Rectangle>(ob);
 	case ControlTypeSize:
diff --git a/test/controls/control_value.cpp b/test/controls/control_value.cpp
index 5084fd0cf..032050a77 100644
--- a/test/controls/control_value.cpp
+++ b/test/controls/control_value.cpp
@@ -318,7 +318,7 @@  protected:
 		/*
 		 * String type.
 		 */
-		std::string string{ "libcamera" };
+		std::string_view string{ "libcamera" };
 		value.set(string);
 		if (value.isNone() || !value.isArray() ||
 		    value.type() != ControlTypeString ||
@@ -327,7 +327,7 @@  protected:
 			return TestFail;
 		}
 
-		if (value.get<std::string>() != string) {
+		if (value.get<std::string_view>() != string) {
 			cerr << "Control value mismatch after setting to string" << endl;
 			return TestFail;
 		}
diff --git a/utils/codegen/controls.py b/utils/codegen/controls.py
index e51610481..9399727bd 100644
--- a/utils/codegen/controls.py
+++ b/utils/codegen/controls.py
@@ -111,7 +111,7 @@  class Control(object):
         size = self.__data.get('size')
 
         if typ == 'string':
-            return 'std::string'
+            return 'std::string_view'
 
         if self.__size is None:
             return typ