[libc++] Overhaul std::quoted; fix its relationship to character traits.
authorArthur O'Dwyer <arthur.j.odwyer@gmail.com>
Fri, 4 Mar 2022 23:24:18 +0000 (18:24 -0500)
committerArthur O'Dwyer <arthur.j.odwyer@gmail.com>
Mon, 7 Mar 2022 18:28:58 +0000 (13:28 -0500)
Move `__quoted_output_proxy` into the one file that uses it.

A `const char*` has no associated traits class, so `std::quoted("literal")`
should be printable into any basic_ostream regardless of traits.

Use hidden-friend `operator<<` and `operator>>`, since we're permitted to.
(The exact signature is unspecified because the class itself is unspecified.)

We shouldn't support `std::quoted("literal")` in C++03 or C++11 mode.
(We do need `std::__quoted(s)` and `std::__quoted(cs)` in C++11 mode,
because they're used by `std::__fs::filesystem::path`.)

Differential Revision: https://reviews.llvm.org/D120135

libcxx/docs/ReleaseNotes.rst
libcxx/include/__string
libcxx/include/iomanip
libcxx/test/std/input.output/iostream.format/quoted.manip/quoted_traits.compile.pass.cpp [new file with mode: 0644]

index 34fd16b..19cc2da 100644 (file)
@@ -66,6 +66,9 @@ API Changes
   (as an extension) ``__int128_t``, and the unsigned versions thereof.
   In particular, ``uniform_int_distribution<int8_t>`` is no longer supported.
 
+- The C++14 function ``std::quoted(const char*)`` is no longer supported in
+  C++03 or C++11 modes.
+
 ABI Changes
 -----------
 
index 203246c..973b699 100644 (file)
@@ -1155,19 +1155,6 @@ size_t __do_string_hash(_Ptr __p, _Ptr __e)
     return __murmur2_or_cityhash<size_t>()(__p, (__e-__p)*sizeof(value_type));
 }
 
-template <class _CharT, class _Iter, class _Traits = char_traits<_CharT> >
-struct __quoted_output_proxy
-{
-    _Iter  __first_;
-    _Iter  __last_;
-    _CharT  __delim_;
-    _CharT  __escape_;
-
-    _LIBCPP_HIDE_FROM_ABI
-    explicit __quoted_output_proxy(_Iter __f, _Iter __l, _CharT __d, _CharT __e)
-        : __first_(__f), __last_(__l), __delim_(__d), __escape_(__e) {}
-};
-
 _LIBCPP_END_NAMESPACE_STD
 
 _LIBCPP_POP_MACROS
index f34c718..c4d8351 100644 (file)
@@ -513,10 +513,12 @@ put_time(const tm* __tm, const _CharT* __fmt)
     return __iom_t10<_CharT>(__tm, __fmt);
 }
 
-template <class _CharT, class _Traits, class _ForwardIterator>
-basic_ostream<_CharT, _Traits>&
+#if _LIBCPP_STD_VER >= 11
+
+template <class _CharT, class _Traits>
+_LIBCPP_HIDE_FROM_ABI basic_ostream<_CharT, _Traits>&
 __quoted_output(basic_ostream<_CharT, _Traits>& __os,
-                _ForwardIterator __first, _ForwardIterator __last, _CharT __delim, _CharT __escape)
+                const _CharT *__first, const _CharT *__last, _CharT __delim, _CharT __escape)
 {
     basic_string<_CharT, _Traits> __str;
     __str.push_back(__delim);
@@ -530,7 +532,7 @@ __quoted_output(basic_ostream<_CharT, _Traits>& __os,
 }
 
 template <class _CharT, class _Traits, class _String>
-basic_istream<_CharT, _Traits>&
+_LIBCPP_HIDE_FROM_ABI basic_istream<_CharT, _Traits>&
 __quoted_input(basic_istream<_CharT, _Traits>& __is, _String& __string, _CharT __delim, _CharT __escape)
 {
     __string.clear();
@@ -546,7 +548,7 @@ __quoted_input(basic_istream<_CharT, _Traits>& __is, _String& __string, _CharT _
         return __is;
     }
 
-    __save_flags<_CharT, _Traits> sf(__is);
+    __save_flags<_CharT, _Traits> __sf(__is);
     std::noskipws(__is);
     while (true) {
         __is >> __c;
@@ -563,16 +565,27 @@ __quoted_input(basic_istream<_CharT, _Traits>& __is, _String& __string, _CharT _
     return __is;
 }
 
-template <class _CharT, class _Traits, class _Iter>
-basic_ostream<_CharT, _Traits>& operator<<(
-         basic_ostream<_CharT, _Traits>& __os,
-         const __quoted_output_proxy<_CharT, _Iter, _Traits>& __proxy)
+template <class _CharT, class _Traits>
+struct _LIBCPP_HIDDEN __quoted_output_proxy
 {
-    return std::__quoted_output(__os, __proxy.__first_, __proxy.__last_, __proxy.__delim_, __proxy.__escape_);
-}
+    const _CharT *__first_;
+    const _CharT *__last_;
+    _CharT __delim_;
+    _CharT __escape_;
+
+    _LIBCPP_HIDE_FROM_ABI
+    explicit __quoted_output_proxy(const _CharT *__f, const _CharT *__l, _CharT __d, _CharT __e)
+        : __first_(__f), __last_(__l), __delim_(__d), __escape_(__e) {}
+
+    template<class _T2, __enable_if_t<_IsSame<_Traits, void>::value || _IsSame<_Traits, _T2>::value>* = nullptr>
+    friend _LIBCPP_HIDE_FROM_ABI basic_ostream<_CharT, _T2>&
+    operator<<(basic_ostream<_CharT, _T2>& __os, const __quoted_output_proxy& __p) {
+        return std::__quoted_output(__os, __p.__first_, __p.__last_, __p.__delim_, __p.__escape_);
+    }
+};
 
 template <class _CharT, class _Traits, class _Allocator>
-struct __quoted_proxy
+struct _LIBCPP_HIDDEN __quoted_proxy
 {
     basic_string<_CharT, _Traits, _Allocator>& __string_;
     _CharT __delim_;
@@ -581,76 +594,66 @@ struct __quoted_proxy
     _LIBCPP_HIDE_FROM_ABI
     explicit __quoted_proxy(basic_string<_CharT, _Traits, _Allocator>& __s, _CharT __d, _CharT __e)
         : __string_(__s), __delim_(__d), __escape_(__e) {}
+
+    friend _LIBCPP_HIDE_FROM_ABI basic_ostream<_CharT, _Traits>&
+    operator<<(basic_ostream<_CharT, _Traits>& __os, const __quoted_proxy& __p) {
+        return std::__quoted_output(__os, __p.__string_.data(), __p.__string_.data() + __p.__string_.size(), __p.__delim_, __p.__escape_);
+    }
+
+    friend _LIBCPP_HIDE_FROM_ABI basic_istream<_CharT, _Traits>&
+    operator>>(basic_istream<_CharT, _Traits>& __is, const __quoted_proxy& __p) {
+        return std::__quoted_input(__is, __p.__string_, __p.__delim_, __p.__escape_);
+    }
 };
 
 template <class _CharT, class _Traits, class _Allocator>
-_LIBCPP_INLINE_VISIBILITY
-basic_ostream<_CharT, _Traits>& operator<<(
-        basic_ostream<_CharT, _Traits>& __os,
-        const __quoted_proxy<_CharT, _Traits, _Allocator>& __proxy)
+_LIBCPP_HIDE_FROM_ABI
+__quoted_output_proxy<_CharT, _Traits>
+__quoted(const basic_string<_CharT, _Traits, _Allocator>& __s, _CharT __delim = _CharT('"'), _CharT __escape = _CharT('\\'))
 {
-    return std::__quoted_output(__os, __proxy.__string_.cbegin(), __proxy.__string_.cend(), __proxy.__delim_, __proxy.__escape_);
+    return __quoted_output_proxy<_CharT, _Traits>(__s.data(), __s.data() + __s.size(), __delim, __escape);
 }
 
-//  extractor for non-const basic_string& proxies
 template <class _CharT, class _Traits, class _Allocator>
-_LIBCPP_INLINE_VISIBILITY
-basic_istream<_CharT, _Traits>& operator>>(
-        basic_istream<_CharT, _Traits>& __is,
-        const __quoted_proxy<_CharT, _Traits, _Allocator>& __proxy)
+_LIBCPP_HIDE_FROM_ABI
+__quoted_proxy<_CharT, _Traits, _Allocator>
+__quoted(basic_string<_CharT, _Traits, _Allocator>& __s, _CharT __delim = _CharT('"'), _CharT __escape = _CharT('\\'))
 {
-    return std::__quoted_input(__is, __proxy.__string_, __proxy.__delim_, __proxy.__escape_);
+    return __quoted_proxy<_CharT, _Traits, _Allocator>(__s, __delim, __escape);
 }
 
+#endif // _LIBCPP_STD_VER >= 11
+
+#if _LIBCPP_STD_VER > 11
+
 template <class _CharT>
-_LIBCPP_INLINE_VISIBILITY
-__quoted_output_proxy<_CharT, const _CharT *>
-quoted(const _CharT *__s, _CharT __delim = _CharT('"'), _CharT __escape = _CharT('\\'))
+_LIBCPP_HIDE_FROM_ABI
+auto quoted(const _CharT *__s, _CharT __delim = _CharT('"'), _CharT __escape = _CharT('\\'))
 {
     const _CharT *__end = __s;
     while (*__end) ++__end;
-    return __quoted_output_proxy<_CharT, const _CharT *>(__s, __end, __delim, __escape);
+    return __quoted_output_proxy<_CharT, void>(__s, __end, __delim, __escape);
 }
 
 template <class _CharT, class _Traits, class _Allocator>
-_LIBCPP_INLINE_VISIBILITY
-__quoted_output_proxy<_CharT, typename basic_string <_CharT, _Traits, _Allocator>::const_iterator>
-__quoted(const basic_string <_CharT, _Traits, _Allocator>& __s, _CharT __delim = _CharT('"'), _CharT __escape = _CharT('\\'))
+_LIBCPP_HIDE_FROM_ABI
+auto quoted(const basic_string<_CharT, _Traits, _Allocator>& __s, _CharT __delim = _CharT('"'), _CharT __escape = _CharT('\\'))
 {
-    return __quoted_output_proxy<_CharT, typename basic_string<_CharT, _Traits, _Allocator>::const_iterator>(__s.cbegin(), __s.cend(), __delim, __escape);
+    return __quoted_output_proxy<_CharT, _Traits>(__s.data(), __s.data() + __s.size(), __delim, __escape);
 }
 
 template <class _CharT, class _Traits, class _Allocator>
-_LIBCPP_INLINE_VISIBILITY
-__quoted_proxy<_CharT, _Traits, _Allocator>
-__quoted(basic_string <_CharT, _Traits, _Allocator>& __s, _CharT __delim = _CharT('"'), _CharT __escape = _CharT('\\'))
+_LIBCPP_HIDE_FROM_ABI
+auto quoted(basic_string<_CharT, _Traits, _Allocator>& __s, _CharT __delim = _CharT('"'), _CharT __escape = _CharT('\\'))
 {
     return __quoted_proxy<_CharT, _Traits, _Allocator>(__s, __delim, __escape);
 }
 
-#if _LIBCPP_STD_VER > 11
-
-template <class _CharT, class _Traits, class _Allocator>
-_LIBCPP_INLINE_VISIBILITY
-__quoted_output_proxy<_CharT, typename basic_string<_CharT, _Traits, _Allocator>::const_iterator>
-quoted(const basic_string<_CharT, _Traits, _Allocator>& __s, _CharT __delim = _CharT('"'), _CharT __escape = _CharT('\\'))
-{
-    return std::__quoted(__s, __delim, __escape);
-}
-
-template <class _CharT, class _Traits, class _Allocator>
-_LIBCPP_INLINE_VISIBILITY
-__quoted_proxy<_CharT, _Traits, _Allocator>
-quoted(basic_string<_CharT, _Traits, _Allocator>& __s, _CharT __delim = _CharT('"'), _CharT __escape = _CharT('\\'))
-{
-    return std::__quoted(__s, __delim, __escape);
-}
-
 template <class _CharT, class _Traits>
-__quoted_output_proxy<_CharT, const _CharT *, _Traits>
-quoted(basic_string_view<_CharT, _Traits> __sv, _CharT __delim = _CharT('"'), _CharT __escape = _CharT('\\'))
+_LIBCPP_HIDE_FROM_ABI
+auto quoted(basic_string_view<_CharT, _Traits> __sv, _CharT __delim = _CharT('"'), _CharT __escape = _CharT('\\'))
 {
-    return __quoted_output_proxy<_CharT, const _CharT *, _Traits>(__sv.data(), __sv.data() + __sv.size(), __delim, __escape);
+    return __quoted_output_proxy<_CharT, _Traits>(__sv.data(), __sv.data() + __sv.size(), __delim, __escape);
 }
 
 #endif // _LIBCPP_STD_VER > 11
diff --git a/libcxx/test/std/input.output/iostream.format/quoted.manip/quoted_traits.compile.pass.cpp b/libcxx/test/std/input.output/iostream.format/quoted.manip/quoted_traits.compile.pass.cpp
new file mode 100644 (file)
index 0000000..35aa54b
--- /dev/null
@@ -0,0 +1,166 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11
+
+// <iomanip>
+
+// std::quoted
+//   Verify that the result type of std::quoted can be streamed to
+//   (and from) ostreams with the correct CharTraits, and not those
+//   with the wrong CharTraits. To avoid our having to create working
+//   ostreams with weird CharTraits, this is a compile-only test.
+
+#include <iomanip>
+#include <istream>
+#include <ostream>
+#include <string>
+#include <string_view>
+#include <type_traits>
+
+#include "test_allocator.h"
+#include "test_macros.h"
+
+template<class IS, class Q>
+decltype(std::declval<IS>() >> std::declval<Q>(), std::true_type())
+has_rightshift_impl(int) { return std::true_type(); }
+
+template<class IS, class Q>
+std::false_type
+has_rightshift_impl(long) { return std::false_type(); }
+
+template<class IS, class Q>
+struct HasRightShift : decltype(has_rightshift_impl<IS, Q>(0)) {};
+
+template<class OS, class Q>
+decltype(std::declval<OS>() << std::declval<Q>(), std::true_type())
+has_leftshift_impl(int) { return std::true_type(); }
+
+template<class OS, class Q>
+std::false_type
+has_leftshift_impl(long) { return std::false_type(); }
+
+template<class OS, class Q>
+struct HasLeftShift : decltype(has_leftshift_impl<OS, Q>(0)) {};
+
+template<class CharT>
+struct FakeCharTraits : std::char_traits<CharT> {};
+
+void test_string_literal()
+{
+  using Q = decltype(std::quoted("hello"));
+  static_assert( HasLeftShift<std::ostream&, Q>::value, "");
+  static_assert(!HasRightShift<std::istream&, Q>::value, "");
+  static_assert( HasLeftShift<std::basic_ostream<char, FakeCharTraits<char>>&, Q>::value, "");
+  static_assert(!HasRightShift<std::basic_istream<char, FakeCharTraits<char>>&, Q>::value, "");
+
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+  using WQ = decltype(std::quoted(L"hello"));
+  static_assert( HasLeftShift<std::wostream&, WQ>::value, "");
+  static_assert(!HasRightShift<std::wistream&, WQ>::value, "");
+  static_assert( HasLeftShift<std::basic_ostream<wchar_t, FakeCharTraits<wchar_t>>&, WQ>::value, "");
+  static_assert(!HasRightShift<std::basic_istream<wchar_t, FakeCharTraits<wchar_t>>&, WQ>::value, "");
+
+  static_assert(!HasLeftShift<std::ostream&, WQ>::value, "");
+  static_assert(!HasLeftShift<std::wostream&, Q>::value, "");
+#endif // TEST_HAS_NO_WIDE_CHARACTERS
+}
+
+void test_std_string()
+{
+  std::string s = "hello";
+  const auto& cs = s;
+  using Q = decltype(std::quoted(s));
+  using CQ = decltype(std::quoted(cs));
+  static_assert( HasLeftShift<std::ostream&, Q>::value, "");
+  static_assert( HasRightShift<std::istream&, Q>::value, "");
+  static_assert( HasLeftShift<std::ostream&, CQ>::value, "");
+  static_assert(!HasRightShift<std::istream&, CQ>::value, "");
+  static_assert(!HasLeftShift<std::basic_ostream<char, FakeCharTraits<char>>&, Q>::value, "");
+  static_assert(!HasRightShift<std::basic_istream<char, FakeCharTraits<char>>&, Q>::value, "");
+  static_assert(!HasLeftShift<std::basic_ostream<char, FakeCharTraits<char>>&, CQ>::value, "");
+  static_assert(!HasRightShift<std::basic_istream<char, FakeCharTraits<char>>&, CQ>::value, "");
+
+  std::basic_string<char, FakeCharTraits<char>, test_allocator<char>> st = "hello";
+  const auto& cst = st;
+  using QT = decltype(std::quoted(st));
+  using CQT = decltype(std::quoted(cst));
+  static_assert(!HasLeftShift<std::ostream&, QT>::value, "");
+  static_assert(!HasRightShift<std::istream&, QT>::value, "");
+  static_assert(!HasLeftShift<std::ostream&, CQT>::value, "");
+  static_assert(!HasRightShift<std::istream&, CQT>::value, "");
+  static_assert( HasLeftShift<std::basic_ostream<char, FakeCharTraits<char>>&, QT>::value, "");
+  static_assert( HasRightShift<std::basic_istream<char, FakeCharTraits<char>>&, QT>::value, "");
+  static_assert( HasLeftShift<std::basic_ostream<char, FakeCharTraits<char>>&, CQT>::value, "");
+  static_assert(!HasRightShift<std::basic_istream<char, FakeCharTraits<char>>&, CQT>::value, "");
+
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+  std::wstring ws = L"hello";
+  const auto& cws = ws;
+  using WQ = decltype(std::quoted(ws));
+  using CWQ = decltype(std::quoted(cws));
+  static_assert( HasLeftShift<std::wostream&, WQ>::value, "");
+  static_assert( HasRightShift<std::wistream&, WQ>::value, "");
+  static_assert( HasLeftShift<std::wostream&, CWQ>::value, "");
+  static_assert(!HasRightShift<std::wistream&, CWQ>::value, "");
+  static_assert(!HasLeftShift<std::basic_ostream<wchar_t, FakeCharTraits<wchar_t>>&, WQ>::value, "");
+  static_assert(!HasRightShift<std::basic_istream<wchar_t, FakeCharTraits<wchar_t>>&, WQ>::value, "");
+  static_assert(!HasLeftShift<std::basic_ostream<wchar_t, FakeCharTraits<wchar_t>>&, CWQ>::value, "");
+  static_assert(!HasRightShift<std::basic_istream<wchar_t, FakeCharTraits<wchar_t>>&, CWQ>::value, "");
+
+  static_assert(!HasLeftShift<std::ostream&, WQ>::value, "");
+  static_assert(!HasLeftShift<std::wostream&, Q>::value, "");
+#endif // TEST_HAS_NO_WIDE_CHARACTERS
+}
+
+void test_std_string_view()
+{
+  std::string_view s = "hello";
+  const auto& cs = s;
+  using Q = decltype(std::quoted(s));
+  using CQ = decltype(std::quoted(cs));
+  static_assert( HasLeftShift<std::ostream&, Q>::value, "");
+  static_assert(!HasRightShift<std::istream&, Q>::value, "");
+  static_assert( HasLeftShift<std::ostream&, CQ>::value, "");
+  static_assert(!HasRightShift<std::istream&, CQ>::value, "");
+  static_assert(!HasLeftShift<std::basic_ostream<char, FakeCharTraits<char>>&, Q>::value, "");
+  static_assert(!HasRightShift<std::basic_istream<char, FakeCharTraits<char>>&, Q>::value, "");
+  static_assert(!HasLeftShift<std::basic_ostream<char, FakeCharTraits<char>>&, CQ>::value, "");
+  static_assert(!HasRightShift<std::basic_istream<char, FakeCharTraits<char>>&, CQ>::value, "");
+
+  std::basic_string_view<char, FakeCharTraits<char>> st = "hello";
+  const auto& cst = st;
+  using QT = decltype(std::quoted(st));
+  using CQT = decltype(std::quoted(cst));
+  static_assert(!HasLeftShift<std::ostream&, QT>::value, "");
+  static_assert(!HasRightShift<std::istream&, QT>::value, "");
+  static_assert(!HasLeftShift<std::ostream&, CQT>::value, "");
+  static_assert(!HasRightShift<std::istream&, CQT>::value, "");
+  static_assert( HasLeftShift<std::basic_ostream<char, FakeCharTraits<char>>&, QT>::value, "");
+  static_assert(!HasRightShift<std::basic_istream<char, FakeCharTraits<char>>&, QT>::value, "");
+  static_assert( HasLeftShift<std::basic_ostream<char, FakeCharTraits<char>>&, CQT>::value, "");
+  static_assert(!HasRightShift<std::basic_istream<char, FakeCharTraits<char>>&, CQT>::value, "");
+
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+  std::wstring_view ws = L"hello";
+  const auto& cws = ws;
+  using WQ = decltype(std::quoted(ws));
+  using CWQ = decltype(std::quoted(cws));
+  static_assert( HasLeftShift<std::wostream&, WQ>::value, "");
+  static_assert(!HasRightShift<std::wistream&, WQ>::value, "");
+  static_assert( HasLeftShift<std::wostream&, CWQ>::value, "");
+  static_assert(!HasRightShift<std::wistream&, CWQ>::value, "");
+  static_assert(!HasLeftShift<std::basic_ostream<wchar_t, FakeCharTraits<wchar_t>>&, WQ>::value, "");
+  static_assert(!HasRightShift<std::basic_istream<wchar_t, FakeCharTraits<wchar_t>>&, WQ>::value, "");
+  static_assert(!HasLeftShift<std::basic_ostream<wchar_t, FakeCharTraits<wchar_t>>&, CWQ>::value, "");
+  static_assert(!HasRightShift<std::basic_istream<wchar_t, FakeCharTraits<wchar_t>>&, CWQ>::value, "");
+
+  static_assert(!HasLeftShift<std::ostream&, WQ>::value, "");
+  static_assert(!HasLeftShift<std::wostream&, Q>::value, "");
+#endif // TEST_HAS_NO_WIDE_CHARACTERS
+}