diff --git a/applications/test/regex1/Test-regex1.C b/applications/test/regex1/Test-regex1.C
index 921b3aa130ca23839c0f6ba2975229f98193098e..0c47eb0cbef63c0d3e40410e84f564e33b2387da 100644
--- a/applications/test/regex1/Test-regex1.C
+++ b/applications/test/regex1/Test-regex1.C
@@ -252,12 +252,16 @@ void testExpressions(const UList<regexTest>& tests)
             }
             else if (re.search(str))
             {
-                Info<< "partial match";
+                Info<< "partial";
             }
             else
             {
                 Info<< "false";
             }
+            if (re.negated())
+            {
+                Info<< " (negated)";
+            }
             Info<< endl;
         }
         catch (const Foam::error& err)
@@ -329,6 +333,15 @@ int main(int argc, char *argv[])
     }
     #endif
 
+    Info<< "sizeof std::regex:  " << sizeof(std::regex) << nl;
+    Info<< "sizeof regex C++11: " << sizeof(regExpCxx) << nl;
+    #ifndef _WIN32
+    Info<< "sizeof regex POSIX: " << sizeof(regExpPosix) << nl;
+    #endif
+    Info<< "sizeof word: " << sizeof(Foam::word) << nl;
+    Info<< "sizeof wordRe: " << sizeof(Foam::wordRe) << nl;
+    Info<< "sizeof keyType: " << sizeof(Foam::keyType) << nl;
+
     if (!args.count({"cxx", "posix"}))
     {
         args.setOption("cxx");
diff --git a/applications/test/regex1/testRegexps2 b/applications/test/regex1/testRegexps2
index 3b4618e305afe9cc08fcf1e2396da869930a7229..080feb5441e5ba446f8c9535dd118e5fe3661b2d 100644
--- a/applications/test/regex1/testRegexps2
+++ b/applications/test/regex1/testRegexps2
@@ -1,7 +1,7 @@
 /*--------------------------------*- C++ -*----------------------------------*\
 | =========                 |                                                 |
 | \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox           |
-|  \\    /   O peration     | Version:  v2106                                 |
+|  \\    /   O peration     | Version:  v2112                                 |
 |   \\  /    A nd           | Website:  www.openfoam.com                      |
 |    \\/     M anipulation  |                                                 |
 \*---------------------------------------------------------------------------*/
@@ -12,6 +12,8 @@
 (
     ( true  "(U|k|epsilon)"  "U" )
     ( false "(U|k|epsilon)"  "alpha" )
+    ( true  "(?!)(U|k|epsilon)"  "alpha" )
+    ( true  "(?! *&)(U|k|epsilon)"  "alpha" )   // Ignore unknown content
     ( true  "ab.*"  "abc" )
     ( true  ".*"  "abc" )
 
diff --git a/src/OSspecific/POSIX/regExp/regExpPosix.C b/src/OSspecific/POSIX/regExp/regExpPosix.C
index dc05361b3c9e6d12b10205085ccee27b87747f53..186d0a611d608bdd9a28d9bf3d2a9701b0760bdf 100644
--- a/src/OSspecific/POSIX/regExp/regExpPosix.C
+++ b/src/OSspecific/POSIX/regExp/regExpPosix.C
@@ -6,7 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2011-2016 OpenFOAM Foundation
-    Copyright (C) 2018 OpenCFD Ltd.
+    Copyright (C) 2018-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -49,88 +49,139 @@ static inline bool fullMatch(const regmatch_t& m, const regoff_t len)
 } // End anonymous namespace
 
 
-// * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * * //
+// * * * * * * * * * * * * * Private Member Functions  * * * * * * * * * * * //
 
-bool Foam::regExpPosix::clear()
+bool Foam::regExpPosix::set_pattern
+(
+    const char* pattern,
+    size_t len,
+    bool ignoreCase
+)
 {
-    if (preg_)
-    {
-        regfree(preg_);
-        delete preg_;
-        preg_ = nullptr;
+    clear();  // Also sets ctrl_ = 0
 
-        return true;
-    }
-
-    return false;
-}
+    const char* pat = pattern;
+    bool doNegate = false;
 
+    // Handle known embedded prefixes
+    if (len > 2 && pat[0] == '(' && pat[1] == '?')
+    {
+        pat += 2;
+        len -= 2;
 
-bool Foam::regExpPosix::set(const char* pattern, bool ignoreCase)
-{
-    clear();
+        for (bool done = false; !done && len; ++pat, --len)
+        {
+            switch (*pat)
+            {
+                case '!':
+                {
+                    // Negated (inverted) match
+                    doNegate = true;
+                    break;
+                }
+                case 'i':
+                {
+                    // Ignore-case
+                    ignoreCase = true;
+                    break;
+                }
+                case ')':
+                {
+                    // End of prefix parsing
+                    done = true;
+                    break;
+                }
+            }
+        }
+    }
 
-    // Avoid nullptr and zero-length patterns
-    if (pattern && *pattern)
+    // Avoid zero-length patterns
+    if (len)
     {
-        int cflags = REG_EXTENDED;
+        int flags = REG_EXTENDED;
         if (ignoreCase)
         {
-            cflags |= REG_ICASE;
+            flags |= REG_ICASE;
         }
 
-        const char* pat = pattern;
-
-        // Check for embedded prefix for ignore-case
-        // this is the only embedded prefix we support
-        // - a simple check is sufficient
-        if (!strncmp(pattern, "(?i)", 4))
         {
-            cflags |= REG_ICASE;
-            pat += 4;
+            preg_ = new regex_t;
+            int err = regcomp(preg_, pat, flags);
 
-            // avoid zero-length patterns
-            if (!*pat)
+            if (err == 0)
             {
-                return false;
+                ctrl_ = (doNegate ? ctrlType::NEGATED : ctrlType::NORMAL);
+                return true;
             }
-        }
-
-        preg_ = new regex_t;
-        int err = regcomp(preg_, pat, cflags);
-
-        if (err != 0)
-        {
-            char errbuf[200];
-            regerror(err, preg_, errbuf, sizeof(errbuf));
+            else
+            {
+                char errbuf[200];
+                regerror(err, preg_, errbuf, sizeof(errbuf));
 
-            FatalErrorInFunction
-                << "Failed to compile regular expression '" << pattern << "'"
-                << nl << errbuf
-                << exit(FatalError);
+                FatalErrorInFunction
+                    << "Failed to compile regular expression '"
+                    << pattern << "'\n" << errbuf
+                    << exit(FatalError);
+            }
         }
-
-        return true;
     }
 
-    return false;  // Was cleared and nothing was set
+    return false;
 }
 
 
-bool Foam::regExpPosix::set(const std::string& pattern, bool ignoreCase)
+// * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * * //
+
+bool Foam::regExpPosix::clear()
 {
-    return set(pattern.c_str(), ignoreCase);
+    ctrl_ = 0;
+
+    if (preg_)
+    {
+        regfree(preg_);
+        delete preg_;
+        preg_ = nullptr;
+        return true;
+    }
+
+    return false;
 }
 
 
 std::string::size_type Foam::regExpPosix::find(const std::string& text) const
 {
-    if (preg_ && !text.empty())
+    // Find with negated is probably not very reliable...
+    if (!preg_ || !ctrl_)
+    {
+        // Undefined: never matches
+        return std::string::npos;
+    }
+    else if (text.empty())
+    {
+        if (ctrl_ == ctrlType::NEGATED)
+        {
+            return 0;  // No match - pretend it starts at position 0
+        }
+        else
+        {
+            return std::string::npos;
+        }
+    }
+    else
     {
         const size_t nmatch = 1;
         regmatch_t pmatch[1];
 
-        if (regexec(preg_, text.c_str(), nmatch, pmatch, 0) == 0)
+        const bool ok = (regexec(preg_, text.c_str(), nmatch, pmatch, 0) == 0);
+
+        if (ctrl_ == ctrlType::NEGATED)
+        {
+            if (!ok)
+            {
+                return 0;  // No match - claim that is starts at position 0
+            }
+        }
+        else if (ok)
         {
             return pmatch[0].rm_so;
         }
@@ -142,23 +193,31 @@ std::string::size_type Foam::regExpPosix::find(const std::string& text) const
 
 bool Foam::regExpPosix::match(const std::string& text) const
 {
-    const auto len = text.size();
+    bool ok = false;
 
-    if (preg_ && len)
+    if (!preg_ || !ctrl_)
+    {
+        // Undefined: never matches
+        return false;
+    }
+
+    const auto len = text.length();
+
+    if (len)
     {
         const size_t nmatch = 1;
         regmatch_t pmatch[1];
 
         // Verify that the entire string was matched
         // - [0] is the entire match result
-        return
+        ok =
         (
             regexec(preg_, text.c_str(), nmatch, pmatch, 0) == 0
          && fullMatch(pmatch[0], len)
         );
     }
 
-    return false;
+    return (ctrl_ == ctrlType::NEGATED ? !ok : ok);
 }
 
 
@@ -170,6 +229,12 @@ bool Foam::regExpPosix::match
 {
     matches.clear();
 
+    // Probably does not make sense for negated pattern...
+    if (negated())
+    {
+        return match(text);
+    }
+
     const auto len = text.size();
     if (preg_ && len)
     {
diff --git a/src/OSspecific/POSIX/regExp/regExpPosix.H b/src/OSspecific/POSIX/regExp/regExpPosix.H
index 74e5854af3b44466ddbccb1426fbf58ce069ac07..585575c326d05b079db68ebe86e58d14b9be7567 100644
--- a/src/OSspecific/POSIX/regExp/regExpPosix.H
+++ b/src/OSspecific/POSIX/regExp/regExpPosix.H
@@ -28,10 +28,20 @@ Class
     Foam::regExpPosix
 
 Description
-    Wrapper around POSIX extended regular expressions.
+    Wrapper around POSIX extended regular expressions
+    with some additional prefix-handling. The prefix-handling is
+    loosely oriented on PCRE regular expressions and provides a
+    simple means of tuning the expressions.
 
-    The PCRE '(?i)' extension is provided to compile the regular expression
-    as being case-insensitive.
+    The prefixes are detected as \c (?...) at the beginning of
+    the regular expression. Any unknown/unsupported prefixes are silently
+    ignored.
+
+    - "(?!i)" :
+       one or more embedded pattern-match modifiers for the entire pattern.
+    - the \c 'i' indicates ignore-case
+    - the \c '!' (exclamation) indicates negated (inverted) matching
+    .
 
 SeeAlso
     The manpage regex(7) for more information about POSIX regular expressions.
@@ -49,8 +59,8 @@ Warning
     for regular expressions continues to improve.
 
 SourceFiles
-    regExpPosixI.H
     regExpPosix.C
+    regExpPosixI.H
 
 \*---------------------------------------------------------------------------*/
 
@@ -74,11 +84,27 @@ template<class StringType> class SubStrings;
 
 class regExpPosix
 {
+    // Data Types
+
+        //- Simple control types
+        enum ctrlType { EMPTY = 0, NORMAL = 1, NEGATED = 2 };
+
+
     // Private Data
 
         //- Compiled regular expression
         regex_t* preg_;
 
+        //- Track if input pattern is non-empty, negated etc.
+        unsigned char ctrl_;
+
+
+    // Private Member Functions
+
+        //- Assign pattern
+        bool set_pattern(const char* pattern, size_t len, bool ignoreCase);
+
+
 public:
 
     // Public Types
@@ -158,7 +184,14 @@ public:
         //- Return true if a precompiled expression exists
         inline bool exists() const noexcept;
 
-        //- The number of capture groups for a non-empty expression
+        //- True if pattern matching is negated
+        inline bool negated() const noexcept;
+
+        //- Change pattern negation, return previous value
+        inline bool negate(bool on) noexcept;
+
+        //- The number of capture groups for a non-empty,
+        //- non-negated expressions
         inline unsigned ngroups() const;
 
 
@@ -173,17 +206,19 @@ public:
 
         //- Compile pattern into a regular expression, optionally ignore case.
         //  \return True if the pattern was compiled
-        bool set(const char* pattern, bool ignoreCase=false);
+        inline bool set(const char* pattern, bool ignoreCase=false);
 
         //- Compile pattern into a regular expression, optionally ignore case.
         //  \return True if the pattern was compiled
-        bool set(const std::string& pattern, bool ignoreCase=false);
+        inline bool set(const std::string& pattern, bool ignoreCase=false);
 
 
     // Matching/Searching
 
         //- Find position within the text.
         //  \return The index where it begins or string::npos if not found
+        //
+        //  \note does not properly work with negated regex!
         std::string::size_type find(const std::string& text) const;
 
         //- True if the regex matches the entire text.
@@ -193,6 +228,8 @@ public:
         //- True if the regex matches the text, set the matches.
         //  The first group starts at index 1 (0 is the entire match).
         //  The begin-of-line (^) and end-of-line ($) anchors are implicit
+        //
+        //  \note does not properly work with negated regex!
         bool match(const std::string& text, results_type& matches) const;
 
         //- Return true if the regex was found within the text
diff --git a/src/OSspecific/POSIX/regExp/regExpPosixI.H b/src/OSspecific/POSIX/regExp/regExpPosixI.H
index d0f7bf69bc69aded1bea42392299833782a78a0d..4217350c694930ae5cc25fa3441e7dc8d333e0db 100644
--- a/src/OSspecific/POSIX/regExp/regExpPosixI.H
+++ b/src/OSspecific/POSIX/regExp/regExpPosixI.H
@@ -31,15 +31,18 @@ License
 
 inline Foam::regExpPosix::regExpPosix() noexcept
 :
-    preg_(nullptr)
+    preg_(nullptr),
+    ctrl_(0)
 {}
 
 
 inline Foam::regExpPosix::regExpPosix(regExpPosix&& rgx) noexcept
 :
-    preg_(rgx.preg_)
+    preg_(rgx.preg_),
+    ctrl_(rgx.ctrl_)
 {
     rgx.preg_ = nullptr;
+    rgx.ctrl_ = 0;
 }
 
 
@@ -49,7 +52,8 @@ inline Foam::regExpPosix::regExpPosix
     const bool ignoreCase
 )
 :
-    preg_(nullptr)
+    preg_(nullptr),
+    ctrl_(0)
 {
     set(pattern, ignoreCase);
 }
@@ -61,7 +65,8 @@ inline Foam::regExpPosix::regExpPosix
     const bool ignoreCase
 )
 :
-    preg_(nullptr)
+    preg_(nullptr),
+    ctrl_(0)
 {
     set(pattern, ignoreCase);
 }
@@ -89,14 +94,72 @@ inline bool Foam::regExpPosix::exists() const noexcept
 }
 
 
+inline bool Foam::regExpPosix::negated() const noexcept
+{
+    return (ctrl_ == ctrlType::NEGATED);
+}
+
+
+inline bool Foam::regExpPosix::negate(bool on) noexcept
+{
+    bool old(ctrl_ == ctrlType::NEGATED);
+
+    if (on)
+    {
+        if (ctrl_)
+        {
+            ctrl_ = ctrlType::NEGATED;
+        }
+    }
+    else if (old)
+    {
+        ctrl_ = ctrlType::NORMAL;
+    }
+
+    return old;
+}
+
+
 inline unsigned Foam::regExpPosix::ngroups() const
 {
-    return preg_ ? preg_->re_nsub : 0;
+    return (preg_ && ctrl_ == ctrlType::NORMAL) ? preg_->re_nsub : 0;
+}
+
+
+inline bool Foam::regExpPosix::set(const char* pattern, bool ignoreCase)
+{
+    // Silently handle nullptr
+    return set_pattern
+    (
+        pattern,
+        (pattern ? std::char_traits<char>::length(pattern) : 0),
+        ignoreCase
+    );
+}
+
+
+inline bool Foam::regExpPosix::set(const std::string& pattern, bool ignoreCase)
+{
+    return set_pattern
+    (
+        pattern.data(),
+        pattern.length(),
+        ignoreCase
+    );
 }
 
 
 inline bool Foam::regExpPosix::search(const std::string& text) const
 {
+    if (!ctrl_)
+    {
+        return false;
+    }
+    else if (text.empty())
+    {
+        return (ctrl_ == ctrlType::NEGATED);
+    }
+
     return std::string::npos != find(text);
 }
 
@@ -107,6 +170,7 @@ inline void Foam::regExpPosix::swap(regExpPosix& rgx)
     {
         // Self-swap is a no-op
         std::swap(preg_, rgx.preg_);
+        std::swap(ctrl_, rgx.ctrl_);
     }
 }
 
diff --git a/src/OpenFOAM/primitives/strings/regex/regExpCxx.C b/src/OpenFOAM/primitives/strings/regex/regExpCxx.C
index 27f0a3598a463987bc5d1c93cf6687f912ed5824..ec9fe6e6316bec008353c735de3d2b40193f65a3 100644
--- a/src/OpenFOAM/primitives/strings/regex/regExpCxx.C
+++ b/src/OpenFOAM/primitives/strings/regex/regExpCxx.C
@@ -5,7 +5,7 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2017-2019 OpenCFD Ltd.
+    Copyright (C) 2017-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -106,103 +106,78 @@ static std::string error_string(const std::regex_error& err)
 } // End anonymous namespace
 
 
-// * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * * //
+// * * * * * * * * * * * * * Private Member Functions  * * * * * * * * * * * //
 
-bool Foam::regExpCxx::set(const char* pattern, bool ignoreCase)
+bool Foam::regExpCxx::set_pattern
+(
+    const char* pattern,
+    size_t len,
+    bool ignoreCase
+)
 {
-    clear();  // Also sets ok_ = false
-
-    size_t len = (pattern ? strlen(pattern) : 0);
-
-    // Avoid nullptr and zero-length patterns
-    if (!len)
-    {
-        return false;
-    }
-
-    std::regex::flag_type flags = syntax();
-    if (ignoreCase)
-    {
-        flags |= std::regex::icase;
-    }
+    clear();  // Also sets ctrl_ = 0
 
     const char* pat = pattern;
+    bool doNegate = false;
 
-    // Has embedded ignore-case prefix?
-    if (len >= 4 && !strncmp(pattern, "(?i)", 4))
+    // Handle known embedded prefixes
+    if (len > 2 && pat[0] == '(' && pat[1] == '?')
     {
-        flags |= std::regex::icase;
-        pat += 4;
-        len -= 4;
-    }
+        pat += 2;
+        len -= 2;
 
-    if (len)
-    {
-        try
+        for (bool done = false; !done && len; ++pat, --len)
         {
-            re_.assign(pat, flags);
-            ok_ = true;
-        }
-        catch (const std::regex_error& err)
-        {
-            FatalErrorInFunction
-                << "Failed to compile regular expression '"
-                << pattern << "'" << nl
-                << err.what() << ": " << error_string(err).c_str() << nl
-                << exit(FatalError);
+            switch (*pat)
+            {
+                case '!':
+                {
+                    // Negated (inverted) match
+                    doNegate = true;
+                    break;
+                }
+                case 'i':
+                {
+                    // Ignore-case
+                    ignoreCase = true;
+                    break;
+                }
+                case ')':
+                {
+                    // End of prefix parsing
+                    done = true;
+                    break;
+                }
+            }
         }
     }
 
-    return ok_;
-}
-
-
-bool Foam::regExpCxx::set(const std::string& pattern, bool ignoreCase)
-{
-    clear();  // Also sets ok_ = false
-
-    auto len = pattern.size();
-
     // Avoid zero-length patterns
-    if (!len)
-    {
-        return false;
-    }
-
-    std::regex::flag_type flags = syntax();
-    if (ignoreCase)
-    {
-        flags |= std::regex::icase;
-    }
-
-    auto pat = pattern.begin();
-
-    // Has embedded ignore-case prefix?
-    if (len >= 4 && !pattern.compare(0, 4, "(?i)"))
-    {
-        flags |= std::regex::icase;
-        pat += 4;
-        len -= 4;
-    }
-
     if (len)
     {
+        std::regex::flag_type flags = syntax();
+        if (ignoreCase)
+        {
+            flags |= std::regex::icase;
+        }
+
         try
         {
-            re_.assign(pat, pattern.end(), flags);
-            ok_ = true;
+            re_.assign(pat, len, flags);
+            ctrl_ = (doNegate ? ctrlType::NEGATED : ctrlType::NORMAL);
+            return true;
         }
         catch (const std::regex_error& err)
         {
             FatalErrorInFunction
                 << "Failed to compile regular expression '"
-                << pattern.c_str() << "'" << nl
+                << pattern << "'" << nl
                 << err.what() << ": " << error_string(err).c_str() << nl
                 << exit(FatalError);
         }
     }
 
-    return ok_;
+    return false;
 }
 
 
diff --git a/src/OpenFOAM/primitives/strings/regex/regExpCxx.H b/src/OpenFOAM/primitives/strings/regex/regExpCxx.H
index a440485cc67ede3fe8cafc156d510f8e762ea873..92d1a353180dc5f9f1a7d96948bf65cc136f53a5 100644
--- a/src/OpenFOAM/primitives/strings/regex/regExpCxx.H
+++ b/src/OpenFOAM/primitives/strings/regex/regExpCxx.H
@@ -27,9 +27,23 @@ Class
     Foam::regExpCxx
 
 Description
-    Wrapper around C++11 regular expressions.
+    Wrapper around C++11 regular expressions
+    with some additional prefix-handling. The prefix-handling is loosely
+    oriented on PCRE regular expressions and provides a simple means of
+    tuning the expressions.
 
-    Using either POSIX extended regular expressions or
+    The prefixes are detected as \c (?...) at the beginning of
+    the regular expression. Any unknown/unsupported prefixes are silently
+    ignored.
+
+    - "(?!i)" :
+       one or more embedded pattern-match modifiers for the entire pattern.
+    - the \c 'i' indicates ignore-case
+    - the \c '!' (exclamation) indicates negated (inverted) matching
+    .
+
+Note
+    Uses either POSIX extended regular expressions or
     <a href=
     "http://www.cplusplus.com/reference/regex/ECMAScript"
     >modified ECMAScript regular expression grammar</a>
@@ -37,10 +51,6 @@ Description
     Since ECMAScript grammar may not work correctly on all installations,
     the current default is to use extended regular expressions.
 
-    The JAVA/PCRE '(?i)' extension is supported as a prefix to compile the
-    regular expression as being case-insensitive.
-
-Note
     The C++11 regular expressions may be broken on some compilers.
     For example, gcc 4.8 is known to fail.
     For these systems the POSIX implementation or alternative must be used.
@@ -50,8 +60,8 @@ Warning
     Use the Foam::regExp typedef instead.
 
 SourceFiles
-    regExpCxxI.H
     regExpCxx.C
+    regExpCxxI.H
 
 \*---------------------------------------------------------------------------*/
 
@@ -72,13 +82,19 @@ namespace Foam
 
 class regExpCxx
 {
+    // Data Types
+
+        //- Simple control types
+        enum ctrlType { EMPTY = 0, NORMAL = 1, NEGATED = 2 };
+
+
     // Private Data
 
         //- Regular expression (using char type)
         std::regex re_;
 
-        //- Track if input pattern was OK - ie, has a length
-        bool ok_;
+        //- Track if input pattern is non-empty, negated etc.
+        unsigned char ctrl_;
 
 
     // Private Member Functions
@@ -87,6 +103,10 @@ class regExpCxx
         //  0 = extended, 1 = ECMAScript
         static inline std::regex::flag_type syntax();
 
+        //- Assign pattern
+        bool set_pattern(const char* pattern, size_t len, bool ignoreCase);
+
+
 public:
 
     // Public Types
@@ -174,13 +194,20 @@ public:
 
     // Access
 
-        //- Return true if expression is empty
+        //- True if expression is empty
         inline bool empty() const noexcept;
 
-        //- Return true if expression is non-empty
+        //- True if expression is non-empty
         inline bool exists() const noexcept;
 
-        //- The number of capture groups for a non-empty expression
+        //- True if pattern matching is negated
+        inline bool negated() const noexcept;
+
+        //- Change pattern negation, return previous value
+        inline bool negate(bool on) noexcept;
+
+        //- The number of capture groups for a non-empty,
+        //- non-negated expressions
         inline unsigned ngroups() const;
 
         //  \return True if the pattern was set with ignore-case.
@@ -198,17 +225,19 @@ public:
 
         //- Compile pattern into a regular expression, optionally ignore case.
         //  \return True if the pattern was compiled
-        bool set(const char* pattern, bool ignoreCase=false);
+        inline bool set(const char* pattern, bool ignoreCase=false);
 
         //- Compile pattern into a regular expression, optionally ignore case.
         //  \return True if the pattern was compiled
-        bool set(const std::string& pattern, bool ignoreCase=false);
+        inline bool set(const std::string& pattern, bool ignoreCase=false);
 
 
     // Matching/Searching
 
         //- Find position within the text.
         //  \return The index where it begins or string::npos if not found
+        //
+        //  \note does not properly work with negated regex!
         inline std::string::size_type find(const std::string& text) const;
 
         //- True if the regex matches the entire text.
@@ -218,6 +247,8 @@ public:
         //- True if the regex matches the text, set the matches.
         //  The first group starts at index 1 (0 is the entire match).
         //  The begin-of-line (^) and end-of-line ($) anchors are implicit
+        //
+        //  \note does not properly work with negated regex!
         inline bool match(const std::string& text, results_type& matches) const;
 
         //- Return true if the regex was found within the text
diff --git a/src/OpenFOAM/primitives/strings/regex/regExpCxxI.H b/src/OpenFOAM/primitives/strings/regex/regExpCxxI.H
index 90043618ff0b859d51d9b49fb924fcc8a8d2c29c..100612ca00434e885db1644df6ad466264eae261 100644
--- a/src/OpenFOAM/primitives/strings/regex/regExpCxxI.H
+++ b/src/OpenFOAM/primitives/strings/regex/regExpCxxI.H
@@ -82,23 +82,23 @@ inline bool Foam::regExpCxx::is_meta
 inline Foam::regExpCxx::regExpCxx()
 :
     re_(),
-    ok_(false)
+    ctrl_(0)
 {}
 
 
 inline Foam::regExpCxx::regExpCxx(const regExpCxx& rgx)
 :
     re_(rgx.re_),
-    ok_(rgx.ok_)
+    ctrl_(rgx.ctrl_)
 {}
 
 
 inline Foam::regExpCxx::regExpCxx(regExpCxx&& rgx) noexcept
 :
     re_(std::move(rgx.re_)),
-    ok_(rgx.ok_)
+    ctrl_(rgx.ctrl_)
 {
-    rgx.ok_ = false;
+    rgx.ctrl_ = 0;
 }
 
 
@@ -109,7 +109,7 @@ inline Foam::regExpCxx::regExpCxx
 )
 :
     re_(),
-    ok_(false)
+    ctrl_(0)
 {
     set(pattern, ignoreCase);
 }
@@ -122,7 +122,7 @@ inline Foam::regExpCxx::regExpCxx
 )
 :
     re_(),
-    ok_(false)
+    ctrl_(0)
 {
     set(pattern, ignoreCase);
 }
@@ -132,34 +132,61 @@ inline Foam::regExpCxx::regExpCxx
 
 inline bool Foam::regExpCxx::empty() const noexcept
 {
-    return !ok_;
+    return !ctrl_;
 }
 
 
 inline bool Foam::regExpCxx::exists() const noexcept
 {
-    return ok_;
+    return ctrl_;
+}
+
+
+inline bool Foam::regExpCxx::negated() const noexcept
+{
+    return (ctrl_ == ctrlType::NEGATED);
+}
+
+
+inline bool Foam::regExpCxx::negate(bool on) noexcept
+{
+    bool old(ctrl_ == ctrlType::NEGATED);
+
+    if (on)
+    {
+        if (ctrl_)
+        {
+            ctrl_ = ctrlType::NEGATED;
+        }
+    }
+    else if (old)
+    {
+        ctrl_ = ctrlType::NORMAL;
+    }
+
+    return old;
 }
 
 
 inline unsigned Foam::regExpCxx::ngroups() const
 {
-    return ok_ ? re_.mark_count() : 0;
+    // Groups only make sense for regular (not negated) matching
+    return ctrl_ == ctrlType::NORMAL ? re_.mark_count() : 0;
 }
 
 
 inline bool Foam::regExpCxx::nocase() const
 {
-    return ok_ && ((re_.flags() & std::regex::icase) == std::regex::icase);
+    return ctrl_ && ((re_.flags() & std::regex::icase) == std::regex::icase);
 }
 
 
 inline bool Foam::regExpCxx::clear()
 {
-    if (ok_)
+    if (ctrl_)
     {
         re_.assign("");
-        ok_ = false;
+        ctrl_ = 0;
 
         return true;
     }
@@ -174,33 +201,102 @@ inline void Foam::regExpCxx::swap(regExpCxx& rgx)
     {
         // Self-swap is a no-op
         re_.swap(rgx.re_);
-        std::swap(ok_, rgx.ok_);
+        std::swap(ctrl_, rgx.ctrl_);
     }
 }
 
 
+inline bool Foam::regExpCxx::set(const char* pattern, bool ignoreCase)
+{
+    // Silently handle nullptr
+    return set_pattern
+    (
+        pattern,
+        (pattern ? std::char_traits<char>::length(pattern) : 0),
+        ignoreCase
+    );
+}
+
+
+inline bool Foam::regExpCxx::set(const std::string& pattern, bool ignoreCase)
+{
+    return set_pattern
+    (
+        pattern.data(),
+        pattern.length(),
+        ignoreCase
+    );
+}
+
+
 inline std::string::size_type
 Foam::regExpCxx::find(const std::string& text) const
 {
-    std::smatch mat;
-    if (!text.empty() && std::regex_search(text, mat, re_))
+    // Find with negated is probably not very reliable...
+    if (!ctrl_)
+    {
+        // Undefined: never matches
+        return std::string::npos;
+    }
+    else if (text.empty())
     {
-        return mat.position(0);
+        if (ctrl_ == ctrlType::NEGATED)
+        {
+            return 0;  // No match - pretend it starts at position 0
+        }
+        else
+        {
+            return std::string::npos;
+        }
+    }
+    else
+    {
+        std::smatch mat;
+
+        const bool ok = std::regex_search(text, mat, re_);
+
+        if (ctrl_ == ctrlType::NEGATED)
+        {
+            if (!ok)
+            {
+                return 0;  // No match - claim that is starts at position 0
+            }
+        }
+        else if (ok)
+        {
+            return mat.position(0);
+        }
     }
 
-    return std::string::npos;
+    return std::string::npos;  // False
 }
 
 
 inline bool Foam::regExpCxx::search(const std::string& text) const
 {
-    return (ok_ && !text.empty() && std::regex_search(text, re_));
+    if (!ctrl_)
+    {
+        // Undefined: never matches
+        return false;
+    }
+
+    const bool ok = (!text.empty() && std::regex_search(text, re_));
+
+    return (ctrl_ == ctrlType::NEGATED) ? !ok : ok;
 }
 
 
 inline bool Foam::regExpCxx::match(const std::string& text) const
 {
-    return (ok_ && !text.empty() && std::regex_match(text, re_));
+    if (!ctrl_)
+    {
+        // Undefined: never matches
+        return false;
+    }
+
+    const bool ok = (!text.empty() && std::regex_match(text, re_));
+
+    return (ctrl_ == ctrlType::NEGATED) ? !ok : ok;
 }
 
 
@@ -210,6 +306,13 @@ inline bool Foam::regExpCxx::match
     std::smatch& matches
 ) const
 {
+    // Probably does not make sense for negated pattern...
+    if (negated())
+    {
+        // clear: matches = std::smatch();
+        return match(text);
+    }
+
     return std::regex_match(text, matches, re_);
 }
 
@@ -228,7 +331,7 @@ inline void Foam::regExpCxx::operator=(const regExpCxx& rgx)
     {
         // Self-assignment is a no-op
         re_ = rgx.re_;
-        ok_ = rgx.ok_;
+        ctrl_ = rgx.ctrl_;
     }
 }