Skip to content

fix: explicitly check operator[] in is_contiguous#4825

Merged
vitaut merged 4 commits into
fmtlib:mainfrom
tearfur:patch-1
Jun 25, 2026
Merged

fix: explicitly check operator[] in is_contiguous#4825
vitaut merged 4 commits into
fmtlib:mainfrom
tearfur:patch-1

Conversation

@tearfur

@tearfur tearfur commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Prevent false positives when the container itself does not have operator[](size_t), but the target type of one of the non-explicit user-defined conversion functions does has a built-in subscript operator. Edit: corrected wording

Example: https://godbolt.org/z/53bqcn97W

Error message
In file included from /opt/compiler-explorer/libs/fmt/trunk/include/fmt/format.h:41,
                 from <source>:3:
/opt/compiler-explorer/libs/fmt/trunk/include/fmt/base.h: In instantiation of 'static constexpr void fmt::v12::detail::container_buffer<Container>::grow(fmt::v12::detail::buffer<typename Container::value_type>&, size_t) [with Container = MyBuf<char, 256>; typename Container::value_type = char; size_t = long unsigned int]':
/opt/compiler-explorer/libs/fmt/trunk/include/fmt/base.h:2029:56:   required from 'fmt::v12::detail::container_buffer<Container>::container_buffer(Container&) [with Container = MyBuf<char, 256>]'
 2029 |       : buffer<value_type>(grow, c.size()), container(c) {}
      |                                                        ^
/opt/compiler-explorer/libs/fmt/trunk/include/fmt/base.h:2046:32:   required from 'fmt::v12::detail::iterator_buffer<OutputIt, typename std::enable_if<(fmt::v12::detail::is_back_insert_iterator<OutputIt>::value && fmt::v12::detail::is_contiguous<typename OutputIt::container_type>::value), typename OutputIt::container_type::value_type>::type>::iterator_buffer(OutputIt, size_t) [with OutputIt = std::back_insert_iterator<MyBuf<char, 256> >; typename std::enable_if<(fmt::v12::detail::is_back_insert_iterator<OutputIt>::value && fmt::v12::detail::is_contiguous<typename OutputIt::container_type>::value), typename OutputIt::container_type::value_type>::type = char; typename OutputIt::container_type = MyBuf<char, 256>; typename OutputIt::container_type::value_type = char; class OutputIt::container_type = MyBuf<char, 256>; size_t = long unsigned int]'
 2046 |       : base(get_container(out)) {}
      |                                ^
/opt/compiler-explorer/libs/fmt/trunk/include/fmt/base.h:2089:10:   required from 'fmt::v12::detail::iterator_buffer<OutputIt, T> fmt::v12::detail::get_buffer(OutputIt) [with T = char; OutputIt = std::back_insert_iterator<MyBuf<char, 256> >; typename std::enable_if<(! is_buffer_appender<OutputIt>::value), int>::type <anonymous> = 0]'
 2089 |   return iterator_buffer<OutputIt, T>(out);
      |          ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/opt/compiler-explorer/libs/fmt/trunk/include/fmt/base.h:2797:40:   required from 'fmt::v12::remove_cvref_t<T> fmt::v12::vformat_to(OutputIt&&, string_view, format_args) [with OutputIt = std::back_insert_iterator<MyBuf<char, 256> >&; typename std::enable_if<detail::is_output_iterator<typename std::remove_cv<typename std::remove_reference<_Tp>::type>::type, char>::value, int>::type <anonymous> = 0; remove_cvref_t<T> = std::back_insert_iterator<MyBuf<char, 256> >; string_view = basic_string_view<char>; format_args = basic_format_args<context>]'
 2797 |   auto&& buf = detail::get_buffer<char>(out);
      |                ~~~~~~~~~~~~~~~~~~~~~~~~^~~~~
/opt/compiler-explorer/libs/fmt/trunk/include/fmt/base.h:2817:20:   required from 'fmt::v12::remove_cvref_t<T> fmt::v12::format_to(OutputIt&&, format_string<T ...>, T&& ...) [with OutputIt = std::back_insert_iterator<MyBuf<char, 256> >; T = {const char (&)[5]}; typename std::enable_if<detail::is_output_iterator<typename std::remove_cv<typename std::remove_reference<_Tp>::type>::type, char>::value, int>::type <anonymous> = 0; remove_cvref_t<T> = std::back_insert_iterator<MyBuf<char, 256> >; format_string<T ...> = fstring<const char (&)[5]>]'
 2817 |   return vformat_to(out, fmt.str, vargs<T...>{{args...}});
      |          ~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
<source>:50:19:   required from here
   50 |     fmt::format_to(std::back_inserter(buf), "{}", "test");
      |     ~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/opt/compiler-explorer/libs/fmt/trunk/include/fmt/base.h:2022:14: error: invalid conversion from 'const char*' to 'char*' [-fpermissive]
 2022 |     self.set(&self.container[0], capacity);
      |              ^~~~~~~~~~~~~~~~
      |              |
      |              const char*
/opt/compiler-explorer/libs/fmt/trunk/include/fmt/base.h:1770:29: note: initializing argument 1 of 'constexpr void fmt::v12::detail::buffer<T>::set(T*, size_t) [with T = char; size_t = long unsigned int]'
 1770 |   FMT_CONSTEXPR void set(T* buf_data, size_t buf_capacity) noexcept {
      |                          ~~~^~~~~~~~
Compiler returned: 1

@tearfur tearfur requested a review from vitaut as a code owner June 24, 2026 17:25

@vitaut vitaut left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM but please add a test case to base-test.

Prevent false positives when the container itself does not have `operator[](size_t)`, but the target type of one of the non-explicit user-defined conversion functions does.
@tearfur tearfur force-pushed the patch-1 branch 3 times, most recently from d21f151 to 28a50d5 Compare June 25, 2026 01:37
@tearfur tearfur requested a review from vitaut June 25, 2026 01:49

@vitaut vitaut left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mostly looks good, just two minor comments.

Comment thread test/base-test.cc Outdated

using value_type = char;

FMT_NODISCARD auto size() const noexcept -> size_t { return buffer_.size(); }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FMT_NODISCARD is not needed, this is just a test. Also please apply clang-format.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

f07287f I didn't notice the incorrect format because the CI passed. Weird.

Comment thread test/base-test.cc Outdated

struct phantom_subscript_operator_container
{
std::vector<char> buffer_;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

buffer_ -> buffer since it is a public member.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tearfur tearfur requested a review from vitaut June 25, 2026 17:41

@vitaut vitaut left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@vitaut vitaut merged commit 0e601c3 into fmtlib:main Jun 25, 2026
46 checks passed
@vitaut

vitaut commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Merged, thanks

@tearfur tearfur deleted the patch-1 branch June 25, 2026 23:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants