Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[libc++] Fix incomplete user-defined ctype specialization in test #74630

Merged
merged 1 commit into from
Dec 13, 2023

Conversation

ldionne
Copy link
Member

@ldionne ldionne commented Dec 6, 2023

The specialization was non-conforming because it was missing a bunch of member functions. Those were missing probably just as an oversight coupled with a bit of laziness -- the rule that user-defined specializations need to match the base template is usually OK to take with a grain of salt, but not when the code is supposed to be portable, which our test suite aims to be.

Fixes #74214

The specialization was non-conforming because it was missing a bunch
of member functions. Those were missing probably just as an oversight
coupled with a bit of laziness -- the rule that user-defined
specializations need to match the base template is usually OK
to take with a grain of salt, but not when the code is supposed
to be portable, which our test suite aims to be.

Fixes llvm#74214
@ldionne ldionne requested a review from a team as a code owner December 6, 2023 17:30
@llvmbot llvmbot added the libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi. label Dec 6, 2023
@llvmbot
Copy link
Member

llvmbot commented Dec 6, 2023

@llvm/pr-subscribers-libcxx

Author: Louis Dionne (ldionne)

Changes

The specialization was non-conforming because it was missing a bunch of member functions. Those were missing probably just as an oversight coupled with a bit of laziness -- the rule that user-defined specializations need to match the base template is usually OK to take with a grain of salt, but not when the code is supposed to be portable, which our test suite aims to be.

Fixes #74214


Full diff: https://github.com/llvm/llvm-project/pull/74630.diff

1 Files Affected:

  • (modified) libcxx/test/std/localization/locale.categories/category.numeric/locale.num.get/user_defined_char_type.pass.cpp (+44-8)
diff --git a/libcxx/test/std/localization/locale.categories/category.numeric/locale.num.get/user_defined_char_type.pass.cpp b/libcxx/test/std/localization/locale.categories/category.numeric/locale.num.get/user_defined_char_type.pass.cpp
index d7b4b816d975b..9a4a2f0d5657e 100644
--- a/libcxx/test/std/localization/locale.categories/category.numeric/locale.num.get/user_defined_char_type.pass.cpp
+++ b/libcxx/test/std/localization/locale.categories/category.numeric/locale.num.get/user_defined_char_type.pass.cpp
@@ -16,8 +16,6 @@
 #include <locale>
 #include <string>
 
-#include "test_macros.h"
-
 struct Char {
   Char() = default;
   Char(char c) : underlying_(c) {}
@@ -73,15 +71,53 @@ struct char_traits<Char> {
   static int_type eof() { return char_traits<char>::eof(); }
 };
 
+// This ctype specialization treats all characters as spaces
 template <>
-class ctype<Char> : public locale::facet {
+class ctype<Char> : public locale::facet, public ctype_base {
 public:
+  using char_type = Char;
   static locale::id id;
-  Char toupper(Char c) const { return Char(std::toupper(c.underlying_)); }
-  const char* widen(const char* first, const char* last, Char* dst) const {
-    for (; first != last;)
-      *dst++ = Char(*first++);
-    return last;
+  explicit ctype(std::size_t refs = 0) : locale::facet(refs) {}
+
+  bool is(mask m, char_type) const { return m & ctype_base::space; }
+  const char_type* is(const char_type* low, const char_type* high, mask* vec) const {
+    for (; low != high; ++low)
+      *vec++ = ctype_base::space;
+    return high;
+  }
+
+  const char_type* scan_is(mask m, const char_type* beg, const char_type* end) const {
+    for (; beg != end; ++beg)
+      if (this->is(m, *beg))
+        return beg;
+    return end;
+  }
+
+  const char_type* scan_not(mask m, const char_type* beg, const char_type* end) const {
+    for (; beg != end; ++beg)
+      if (!this->is(m, *beg))
+        return beg;
+    return end;
+  }
+
+  char_type toupper(char_type c) const { return c; }
+  const char_type* toupper(char_type*, const char_type* end) const { return end; }
+
+  char_type tolower(char_type c) const { return c; }
+  const char_type* tolower(char_type*, const char_type* end) const { return end; }
+
+  char_type widen(char c) const { return char_type(c); }
+  const char* widen(const char* beg, const char* end, char_type* dst) const {
+    for (; beg != end; ++beg, ++dst)
+      *dst = char_type(*beg);
+    return end;
+  }
+
+  char narrow(char_type c, char /*dflt*/) const { return c.underlying_; }
+  const char_type* narrow(const char_type* beg, const char_type* end, char /*dflt*/, char* dst) const {
+    for (; beg != end; ++beg, ++dst)
+      *dst = beg->underlying_;
+    return end;
   }
 };
 

template <>
class ctype<Char> : public locale::facet {
class ctype<Char> : public locale::facet, public ctype_base {
Copy link
Member

Choose a reason for hiding this comment

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

I'm getting errors here:

D:\GitHub\STL\llvm-project\libcxx\test\std\localization\locale.categories\category.numeric\locale.num.get\user_defined_char_type.pass.cpp(76,21): error: direct base 'locale::facet' is inaccessible due to ambiguity:
    class std::ctype<Char> -> class locale::facet
    class std::ctype<Char> -> ctype_base -> class locale::facet [-Werror,-Winaccessible-base]
class ctype<Char> : public locale::facet, public ctype_base {
                    ^~~~~~~~~~~~~~~~~~~~
D:\GitHub\STL\out\x64\out\inc\xlocale(454,12): error: ambiguous cast from base 'std::locale::facet' to derived 'std::ctype<Char>':
    class locale::facet -> const class std::ctype<Char>
    class locale::facet -> ctype_base -> const class std::ctype<Char>
    return static_cast<const _Facet&>(*_Pf); // should be dynamic_cast
           ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

I see that your PR is imitating [locale.ctype.general] which depicts template<class charT> class ctype : public locale::facet, public ctype_base. Unfortunately, microsoft/STL is weird - we have ctype_base deriving from locale::facet (which we shouldn't, but fixing it would break ABI), then our ctype derives from only ctype_base.

I'm not sure how to work around this divergence without adding an ifdef for MSVC's STL to this test. I'd be okay with you merging this PR as-is, then I'll look into what else is needed to get it to pass for us.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah I noticed that you folks had locale::facet deriving from ctype_base (or the other way around) when I tested this on Godbolt. I think it makes it impossible for users to implement a specialization of ctype in a conforming way, but I understand this is not really something you can fix.

I'd be OK with an #ifdef for this case.

@ldionne
Copy link
Member Author

ldionne commented Dec 13, 2023

So I'm going to merge this and then @StephanTLavavej can fix it up for MSVC.

@ldionne ldionne merged commit 785e094 into llvm:main Dec 13, 2023
43 of 45 checks passed
@ldionne ldionne deleted the review/user-defined-ctype branch December 13, 2023 15:27
@StephanTLavavej
Copy link
Member

Thank you! 😻

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[libc++][test] User-defined std::ctype<Char> specialization lacks tolower()
3 participants