Repository: AMReX-Codes/amrex — pull request #4144 (cpp)
Title: Fix ParmParse::query_enum_case_insensitive

Since #4143, we can no longer cast the position of the found string in the vector of all names to obtain its enum value, because the enum values can now have duplicates.


Resolved issues:
title: AMREX_ENUM: Add more capabilites
body: Add support for `=`. For example,

```c++
    AMREX_ENUM(MyColor,
               red,
               chi=red,
               green,
               blue,
               Default = green);
```
The underlying values are then
```c++
    { red = 0,
      chi = 0,
      green = 1,
      blue = 2,
      Default = 1}
```
We can then have
```c++
    assert(amrex::getEnum<MyColor2>("red") == MyColor2::red);
    assert(amrex::getEnum<MyColor2>("chi") == MyColor2::chi);
    assert(amrex::getEnum<MyColor2>("green") == MyColor2::green);
    assert(amrex::getEnum<MyColor2>("blue") == MyColor2::blue);
    assert(amrex::getEnum<MyColor2>("Default") == MyColor2::Default);
```
Because of there are duplicated values, using value to get name string will return the first matched string. Currently, I don't see a way to fix it. But, since `MyColor::red == MyColor::chi` is true, it's also not outrageously wrong.
```c++
    assert(amrex::getEnumNameString(MyColor2::red) == "red");
    assert(amrex::getEnumNameString(MyColor2::chi) == "red");
    assert(amrex::getEnumNameString(MyColor2::green) == "green");
    assert(amrex::getEnumNameString(MyColor2::blue) == "blue");
    assert(amrex::getEnumNameString(MyColor2::Default) == "green");
```
P.S. Chi means red in Chinese. https://en.wikipedia.org/wiki/Radical_155


Hints:

Fix patch:
diff --git a/Src/Base/AMReX_Enum.H b/Src/Base/AMReX_Enum.H
index 2bb503d4113..b94e30d396e 100644
--- a/Src/Base/AMReX_Enum.H
+++ b/Src/Base/AMReX_Enum.H
@@ -67,6 +67,26 @@ namespace amrex
         }
     }
 
+    template <typename T, typename ET = amrex_enum_traits<T>,
+              std::enable_if_t<ET::value,int> = 0>
+    T getEnumCaseInsensitive (std::string_view const& s)
+    {
+        auto const& kv = getEnumNameValuePairs<T>();
+        std::string ls = amrex::toLower(std::string(s));
+        auto it = std::find_if(kv.begin(), kv.end(),
+                               [&] (auto const& x) -> bool
+                                   { return amrex::toLower(x.first) == ls; });
+        if (it != kv.end()) {
+            return it->second;
+        } else {
+            std::string error_msg("amrex::getEnumCaseInsensitive: Unknown enum: ");
+            error_msg.append(s).append(" in AMREX_ENUM(").append(ET::class_name)
+                .append(", ").append(ET::enum_names).append(").");
+            throw std::runtime_error(error_msg);
+            return T();
+        }
+    }
+
     template <typename T, typename ET = amrex_enum_traits<T>,
               std::enable_if_t<ET::value,int> = 0>
     std::string getEnumNameString (T const& v)
diff --git a/Src/Base/AMReX_ParmParse.H b/Src/Base/AMReX_ParmParse.H
index 63c0c45431a..42e62a05ab3 100644
--- a/Src/Base/AMReX_ParmParse.H
+++ b/Src/Base/AMReX_ParmParse.H
@@ -1329,19 +1329,14 @@ public:
         std::string s;
         int exist = this->query(name, s, ival);
         if (exist) {
-            s = amrex::toLower(s);
-            auto const& enum_names = amrex::getEnumNameStrings<T>();
-            auto found = std::find_if(enum_names.begin(), enum_names.end(),
-                                      [&] (std::string const& ename) {
-                                          return amrex::toLower(ename) == s;
-                                      });
-            if (found != enum_names.end()) {
-                ref = static_cast<T>(std::distance(enum_names.begin(), found));
-            } else {
-                std::string msg("query_enum_case_insensitive(\"");
-                msg.append(name).append("\",").append(amrex::getEnumClassName<T>())
-                    .append("&) failed.");
-                throw std::runtime_error(msg);
+            try {
+                ref = amrex::getEnumCaseInsensitive<T>(s);
+            } catch (...) {
+                if (amrex::Verbose() > 0) {
+                    amrex::Print() << "amrex::ParmParse::query_enum_case_insensitive (input name: "
+                                   << this->prefixedName(name) << "):\n";
+                }
+                throw;
             }
         }
         return exist;


Test patch:
diff --git a/Tests/Enum/main.cpp b/Tests/Enum/main.cpp
index 69ed91adc27..b0647836e49 100644
--- a/Tests/Enum/main.cpp
+++ b/Tests/Enum/main.cpp
@@ -129,6 +129,11 @@ int main (int argc, char* argv[])
         AMREX_ALWAYS_ASSERT(amrex::getEnum<MyColor2>("green") == MyColor2::green);
         AMREX_ALWAYS_ASSERT(amrex::getEnum<MyColor2>("blue") == MyColor2::blue);
         AMREX_ALWAYS_ASSERT(amrex::getEnum<MyColor2>("Default") == MyColor2::Default);
+
+        auto my_blue = MyColor2::Default;
+        ParmParse pp;
+        pp.query_enum_case_insensitive("color5", my_blue);
+        AMREX_ALWAYS_ASSERT(my_blue == MyColor2::blue);
     }
 
     amrex::Finalize();