diff --git a/applications/test/exprValue1/Test-exprValue1.cxx b/applications/test/exprValue1/Test-exprValue1.cxx
index df77fd99567b06e44717983c3847c83b8a64056d..2a1f41564c1a42cc68d638cb965a7315ce896fda 100644
--- a/applications/test/exprValue1/Test-exprValue1.cxx
+++ b/applications/test/exprValue1/Test-exprValue1.cxx
@@ -5,7 +5,7 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2021-2023 OpenCFD Ltd.
+    Copyright (C) 2021-2024 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM, distributed under GPL-3.0-or-later.
@@ -21,6 +21,8 @@ Description
 #include "argList.H"
 #include "IOstreams.H"
 #include "ITstream.H"
+#include "OTstream.H"
+#include "SpanStream.H"
 #include "exprValue.H"
 #include "Pstream.H"
 
@@ -34,6 +36,41 @@ void printInfo(const expressions::exprValue& val)
 }
 
 
+void write_read(const expressions::exprValue& val)
+{
+    OCharStream os;
+    os << val;
+
+    ISpanStream is(os.view());
+    expressions::exprValue val2;
+    is >> val2;
+
+    Pout<< "wrote " << os.count() << " chars: " << os.str() << nl;
+
+    printInfo(val);
+    printInfo(val2);
+    Pout<< "====" << nl;
+}
+
+
+tokenList tokens_of(const expressions::exprValue& val)
+{
+    OTstream toks;
+    toks << val;
+
+    Pout<< "val with tokens: ";
+    toks.writeList(Pout, 0) << nl;
+
+    for (const auto& t : toks)
+    {
+        Pout<< "  " << t.info() << nl;
+    }
+    Pout<< nl;
+
+    return toks;
+}
+
+
 expressions::exprValue tryParse(const std::string& str)
 {
     expressions::exprValue val, val2;
@@ -90,6 +127,7 @@ int main(int argc, char *argv[])
 
     {
         expressions::exprValue value;
+        tokenList toks;
 
         Info<< "exprValue"
             << " sizeof:" << value.size_bytes()
@@ -99,21 +137,31 @@ int main(int argc, char *argv[])
 
         // Nothing
         printInfo(value);
+        toks = tokens_of(value);
+        write_read(value);
 
         value.set(scalar(100));
-        printInfo(value);
+        printInfo(value); write_read(value); toks = tokens_of(value);
+
+        value.set(scalar(100.01));
+        printInfo(value); write_read(value); toks = tokens_of(value);
 
         value.set(vector(1,2,3));
-        printInfo(value);
+        printInfo(value); write_read(value); toks = tokens_of(value);
 
         value = vector(4,5,6);
-        printInfo(value);
+        printInfo(value); write_read(value); toks = tokens_of(value);
 
         value = Zero;
-        printInfo(value);
+        printInfo(value); write_read(value); toks = tokens_of(value);
 
         value.clear();
+        printInfo(value); write_read(value); toks = tokens_of(value);
+
+        value.set<bool>(true);
+
         printInfo(value);
+        printInfo(value); write_read(value); toks = tokens_of(value);
 
         if (UPstream::parRun())
         {
diff --git a/applications/test/exprValue2/Make/files b/applications/test/exprValue2/Make/files
index c49a9ce705969bd29854da64b3919838a22b383a..42c8232f685915f48122a1f041736cf0eb8cf526 100644
--- a/applications/test/exprValue2/Make/files
+++ b/applications/test/exprValue2/Make/files
@@ -1,5 +1,3 @@
-exprValueFieldTag.cxx
-
 Test-exprValue2.cxx
 
 EXE = $(FOAM_USER_APPBIN)/Test-exprValue2
diff --git a/applications/test/exprValue2/Test-exprValue2.cxx b/applications/test/exprValue2/Test-exprValue2.cxx
index 93897ae321ea7221c117957cc6e84ce617eec193..49f75f24b4d37db001ea3de6873c79f5f8f66727 100644
--- a/applications/test/exprValue2/Test-exprValue2.cxx
+++ b/applications/test/exprValue2/Test-exprValue2.cxx
@@ -5,7 +5,7 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2023 OpenCFD Ltd.
+    Copyright (C) 2023-2024 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -36,7 +36,6 @@ Description
 #include "vectorField.H"
 #include "DynamicList.H"
 #include "Random.H"
-#include "exprValue.H"
 #include "exprValueFieldTag.H"
 
 using namespace Foam;
@@ -61,27 +60,81 @@ int main(int argc, char *argv[])
 
     #include "setRootCase.H"
 
+    DynamicList<fieldTag> allTags;
+
     {
         scalarField fld1(20);
         scalarField fld2a(20, Zero);
         scalarField fld2b(10, 3.10);
         scalarField fld3;
 
-        forAll(fld1, i)
+        for (auto& val : fld1)
+        {
+            val = rnd.position<scalar>(0, 20);
+        }
+
+        if (!UPstream::master())
         {
-            fld1[i] = rnd.position<scalar>(0, 20);
+            fld2b.resize(5);
+            fld2b *= 2;
         }
 
         fieldTag tag1(fld1.begin(), fld1.end());
         fieldTag tag2a(fld2a.begin(), fld2a.end());
         fieldTag tag2b(fld2b.begin(), fld2b.end());
         fieldTag tag3(fld3.begin(), fld3.end());
+        fieldTag tag4(fld3.begin(), fld3.end());
 
         printInfo(tag1) << nl;
         printInfo(tag2a) << nl;
         printInfo(tag2b) << nl;
         printInfo(tag3) << nl;
 
+        {
+            Pout<< "Test reduce" << nl;
+
+            fieldTag work(fld2b.begin(), fld2b.end());
+
+            Pout<< "Before" << nl;
+            printInfo(work) << nl;
+
+            work.reduce();
+
+            Pout<< "After" << nl;
+            printInfo(work) << nl;
+            Pout<< "====" << nl;
+        }
+
+        allTags.clear();
+        allTags.push_back(tag1);
+        allTags.push_back(tag2a);
+        allTags.push_back(tag2b);
+        allTags.push_back(tag3);
+        allTags.push_back(tag4);
+        allTags.push_back(fieldTag::make_empty<tensor>());
+
+
+        // Add some other types
+        {
+            vectorField vfld2a(20, vector::uniform(1.23));
+
+            allTags.emplace_back
+            (
+                vfld2a.begin(),
+                vfld2a.end()
+            );
+            allTags.emplace_back(vector(1.01, 2.02, 3.03));
+            allTags.emplace_back(12.4);
+
+            allTags.emplace_back().set_value(vector::uniform(2.0));
+            allTags.back().set_empty();
+        }
+        Info<< "all tags: " << allTags << nl;
+
+        Foam::sort(allTags);
+
+        Info<< "sorted: " << allTags << nl;
+
         fieldTag result;
 
         result = fieldTag::combineOp{}(tag1, tag2a);
diff --git a/applications/test/exprValue2/exprValueFieldTag.cxx b/applications/test/exprValue2/exprValueFieldTag.cxx
deleted file mode 100644
index e30226107bc9b2946080e84bf8fd57d271679f88..0000000000000000000000000000000000000000
--- a/applications/test/exprValue2/exprValueFieldTag.cxx
+++ /dev/null
@@ -1,160 +0,0 @@
-/*---------------------------------------------------------------------------*\
-  =========                 |
-  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
-   \\    /   O peration     |
-    \\  /    A nd           | www.openfoam.com
-     \\/     M anipulation  |
--------------------------------------------------------------------------------
-    Copyright (C) 2023 OpenCFD Ltd.
--------------------------------------------------------------------------------
-License
-    This file is part of OpenFOAM.
-
-    OpenFOAM is free software: you can redistribute it and/or modify it
-    under the terms of the GNU General Public License as published by
-    the Free Software Foundation, either version 3 of the License, or
-    (at your option) any later version.
-
-    OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
-    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
-    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
-    for more details.
-
-    You should have received a copy of the GNU General Public License
-    along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
-
-\*---------------------------------------------------------------------------*/
-
-#include "exprValueFieldTag.H"
-
-// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
-
-bool Foam::expressions::exprValueFieldTag::empty() const noexcept
-{
-    return
-    (
-        uniformity_ == Foam::Detail::ListPolicy::uniformity::EMPTY
-    );
-}
-
-
-bool Foam::expressions::exprValueFieldTag::is_uniform() const noexcept
-{
-    return
-    (
-        uniformity_ == Foam::Detail::ListPolicy::uniformity::UNIFORM
-    );
-}
-
-
-bool Foam::expressions::exprValueFieldTag::is_nonuniform() const noexcept
-{
-    return
-    (
-        uniformity_ == Foam::Detail::ListPolicy::uniformity::NONUNIFORM
-    );
-}
-
-
-bool Foam::expressions::exprValueFieldTag::equal
-(
-    const exprValueFieldTag& rhs
-) const
-{
-    return (value_ == rhs.value_);
-}
-
-
-void Foam::expressions::exprValueFieldTag::set_nouniform() noexcept
-{
-    uniformity_ = Foam::Detail::ListPolicy::uniformity::NONUNIFORM;
-    value_ = Foam::zero{};
-}
-
-
-void Foam::expressions::exprValueFieldTag::combine
-(
-    const exprValueFieldTag& b
-)
-{
-    if (b.empty())
-    {
-        // no-op
-        return;
-    }
-
-    exprValueFieldTag& a = *this;
-
-    if (a.empty())
-    {
-        a = b;
-    }
-    else if (a.is_nonuniform())
-    {
-        // Already non-uniform/mixed
-        // a.uniformity_ |= b.uniformity_;
-
-        a.value_ = Foam::zero{};
-    }
-    else if (a.is_uniform() && b.is_uniform())
-    {
-        // Both are uniform, but are they the same value?
-        if (!a.equal(b))
-        {
-            a.set_nouniform();
-        }
-    }
-}
-
-
-// * * * * * * * * * * * * * * * IOstream Operators  * * * * * * * * * * * * //
-
-void Foam::expressions::exprValueFieldTag::read(Istream& is)
-{
-    label uniformTag;
-
-    is >> uniformTag;
-    uniformity_ = int(uniformTag);
-    value_.read(is);
-}
-
-
-void Foam::expressions::exprValueFieldTag::write(Ostream& os) const
-{
-    os << label(uniformity_);
-    value_.write(os, false);  // No pruning
-}
-
-
-void Foam::expressions::exprValueFieldTag::print(Ostream& os) const
-{
-    os  << "{ uniform:"
-        << label(uniformity_)
-        << " type:" << label(value_.typeCode())
-        << " value: " << value_ << " }";
-}
-
-
-Foam::Istream& Foam::operator>>
-(
-    Istream& is,
-    expressions::exprValueFieldTag& tag
-)
-{
-    tag.read(is);
-    return is;
-}
-
-
-Foam::Ostream& Foam::operator<<
-(
-    Ostream& os,
-    const expressions::exprValueFieldTag& tag
-)
-{
-    tag.write(os);
-    return os;
-}
-
-
-// ************************************************************************* //
diff --git a/src/OpenFOAM/Make/files b/src/OpenFOAM/Make/files
index 3c994955373fea53ba6eefc4a8c4a5532c5d7359..f8df675d93d013772900428798bb6ff33d568eb8 100644
--- a/src/OpenFOAM/Make/files
+++ b/src/OpenFOAM/Make/files
@@ -185,6 +185,7 @@ $(expr)/scanToken/exprScanToken.C
 
 $(expr)/traits/exprTraits.C
 $(expr)/value/exprValue.C
+$(expr)/value/exprValueFieldTag.C
 
 $(expr)/exprDriver/exprDriver.C
 $(expr)/exprDriver/exprDriverFields.C
diff --git a/src/OpenFOAM/expressions/exprResult/exprResult.H b/src/OpenFOAM/expressions/exprResult/exprResult.H
index cd7f3d6177d09c7e0b516d497f55fdec29cbdbfd..e712e1c440ff804a864d4c800e5b5bbe09499fcd 100644
--- a/src/OpenFOAM/expressions/exprResult/exprResult.H
+++ b/src/OpenFOAM/expressions/exprResult/exprResult.H
@@ -146,11 +146,11 @@ class exprResult
         //- Type-checked determination of centre value (min/max)
         //  \return True if the type check was satisfied
         template<class Type>
-        bool setAverageValueChecked(const bool parRun = Pstream::parRun());
+        bool setAverageValueChecked(const bool parRun = UPstream::parRun());
 
         //- Type-checked determination of average bool value
         //  \return True if the type check was satisfied
-        bool setAverageValueCheckedBool(const bool parRun = Pstream::parRun());
+        bool setAverageValueCheckedBool(const bool parRun = UPstream::parRun());
 
         //- Type-checked copy of field
         //  \return True if the type check was satisfied
@@ -385,7 +385,7 @@ public:
         //- Test if field corresponds to a single-value and thus uniform.
         //  Uses field min/max to establish uniformity.
         //  Test afterwards with isUniform()
-        void testIfSingleValue(const bool parRun = Pstream::parRun());
+        void testIfSingleValue(const bool parRun = UPstream::parRun());
 
 
     // Set results
@@ -437,7 +437,7 @@ public:
         (
             const label size,
             const bool noWarn,
-            const bool parRun = Pstream::parRun()
+            const bool parRun = UPstream::parRun()
         ) const;
 
         //- Get a reduced result
diff --git a/src/OpenFOAM/expressions/value/exprValue.C b/src/OpenFOAM/expressions/value/exprValue.C
index bfddba1137ac806c98c7443c571bb67faf7e8366..7272979d2c647d1cc90c978b0655f9d0e7dd63a9 100644
--- a/src/OpenFOAM/expressions/value/exprValue.C
+++ b/src/OpenFOAM/expressions/value/exprValue.C
@@ -5,7 +5,7 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2021-2023 OpenCFD Ltd.
+    Copyright (C) 2021-2024 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -26,24 +26,42 @@ License
 \*---------------------------------------------------------------------------*/
 
 #include "exprValue.H"
+#include "error.H"
 #include "ITstream.H"
 #include "Switch.H"
 #include <cstring>  // For memcpy, memset
 
+// * * * * * * * * * * * * * * * *  Details  * * * * * * * * * * * * * * * * //
+
+void Foam::expressions::Detail::exprValueUnion::notSpecialized
+(
+    const std::string& msg
+) noexcept
+{
+    FatalErrorInFunction
+        << "non-specialized: " << msg.c_str() << endl
+        << abort(FatalError);
+}
+
+
 // * * * * * * * * * * * * * * * Local Functions * * * * * * * * * * * * * * //
 
 namespace Foam
 {
 
+#if 0
+// General way to add tokens for VectorSpace types
+// (caller excludes none/invalid)
 template<class Type>
-static void fillTokens(const Type& val, tokenList& toks)
+static void addTokens(tokenList& toks, const Type& val)
 {
     const direction nCmpt = pTraits<Type>::nComponents;
     const direction nParen = 2*(pTraits<Type>::rank || (nCmpt > 1) ? 1 : 0);
 
-    toks.resize_nocopy(nCmpt + nParen);
+    const label nOld = toks.size();
+    toks.resize(nOld + label(nCmpt + nParen));
 
-    auto iter = toks.begin();
+    auto iter = toks.begin(nOld);
 
     if (nParen)
     {
@@ -67,28 +85,57 @@ static void fillTokens(const Type& val, tokenList& toks)
 
 //- Specialized for bool
 template<>
-void fillTokens<bool>(const bool& val, tokenList& toks)
+void addTokens<bool>(tokenList& toks, const bool& val)
 {
-    toks.resize_nocopy(1);
-    toks.front() = token::boolean(val);
+    toks.emplace_back() = token::boolean(val);
 }
 
 
 //- Specialized for label
 template<>
-void fillTokens<label>(const label& val, tokenList& toks)
+void addTokens<label>(tokenList& toks, const label& val)
 {
-    toks.resize_nocopy(1);
-    toks.front() = val;
+    toks.emplace_back() = val;
 }
 
 
 //- Specialized for scalar
 template<>
-void fillTokens<scalar>(const scalar& val, tokenList& toks)
+void addTokens<scalar>(tokenList& toks, const scalar& val)
+{
+    toks.emplace_back() = val;
+}
+
+#endif
+
+
+// General output of type (caller excludes none/invalid)
+template<class Type>
+static void putType(Ostream& os, const Type& val)
+{
+    os  << val;
+}
+
+
+//- Specialized for bool.
+//- Write as (true/false) via Switch to avoid bool/label ambiguity
+template<>
+void putType<bool>(Ostream& os, const bool& val)
+{
+    // Note: prefer Switch() vs (std::ios::boolalpha) to avoid any
+    // potential locale issues.
+    os  << Switch(val);
+}
+
+
+//- Specialized for scalar.
+//- Write with '.' to avoid scalar/label ambiguity
+template<>
+void putType<scalar>(Ostream& os, const scalar& val)
 {
-    toks.resize_nocopy(1);
-    toks.front() = val;
+    const auto oldflags = os.setf(std::ios::showpoint);
+    os  << val;
+    os.flags(oldflags);  // Restore
 }
 
 } // End namespace Foam
@@ -226,6 +273,7 @@ void Foam::expressions::exprValue::deepCopy(const exprValue& rhs)
 }
 
 
+#if 0
 Foam::tokenList Foam::expressions::exprValue::tokens(bool prune) const
 {
     // Handling for NONE, INVALID:
@@ -237,24 +285,28 @@ Foam::tokenList Foam::expressions::exprValue::tokens(bool prune) const
 
     tokenList toks;
 
-    if (!prune)
+    switch (typeCode_)
     {
-        if (typeCode_ == expressions::valueTypeCode::NONE)
+        case expressions::valueTypeCode::NONE :
         {
-            toks.resize(2);
-            toks.front() = token::BEGIN_LIST;
-            toks.back() = token::END_LIST;
-            return toks;
+            if (!prune)
+            {
+                toks.resize(2);
+                toks.front() = token::BEGIN_LIST;
+                toks.back() = token::END_LIST;
+            }
+            break;
         }
-        else if (typeCode_ == expressions::valueTypeCode::INVALID)
+
+        case expressions::valueTypeCode::INVALID :
         {
-            toks.emplace_back(word("bad"));
-            return toks;
+            if (!prune)
+            {
+                toks.emplace_back(word("bad"));
+            }
+            break;
         }
-    }
 
-    switch (typeCode_)
-    {
         #undef  doLocalCode
         #define doLocalCode(Type, UnusedParam)                        \
                                                                       \
@@ -263,7 +315,7 @@ Foam::tokenList Foam::expressions::exprValue::tokens(bool prune) const
             const Type* dataPtr = data_.get<Type>();                  \
             if (dataPtr)                                              \
             {                                                         \
-                fillTokens<Type>(*dataPtr, toks);                     \
+                addTokens<Type>(toks, *dataPtr);                      \
             }                                                         \
             break;                                                    \
         }
@@ -277,6 +329,7 @@ Foam::tokenList Foam::expressions::exprValue::tokens(bool prune) const
 
     return toks;
 }
+#endif
 
 
 void Foam::expressions::exprValue::write(Ostream& os, bool prune) const
@@ -288,22 +341,26 @@ void Foam::expressions::exprValue::write(Ostream& os, bool prune) const
     // With prune:
     //   - no output for either
 
-    if (!prune)
+    switch (typeCode_)
     {
-        if (typeCode_ == expressions::valueTypeCode::NONE)
+        case expressions::valueTypeCode::NONE :
         {
-            os << token::BEGIN_LIST << token::END_LIST;
-            return;
+            if (!prune)
+            {
+                os << token::BEGIN_LIST << token::END_LIST;
+            }
+            break;
         }
-        else if (typeCode_ == expressions::valueTypeCode::INVALID)
+
+        case expressions::valueTypeCode::INVALID :
         {
-            os << word("bad");
-            return;
+            if (!prune)
+            {
+                os << word("bad");
+            }
+            break;
         }
-    }
 
-    switch (typeCode_)
-    {
         #undef  doLocalCode
         #define doLocalCode(Type, UnusedParam)                        \
                                                                       \
@@ -312,7 +369,7 @@ void Foam::expressions::exprValue::write(Ostream& os, bool prune) const
             const Type* dataPtr = data_.get<Type>();                  \
             if (dataPtr)                                              \
             {                                                         \
-                os << *dataPtr;                                       \
+                putType(os, *dataPtr);                                \
             }                                                         \
             break;                                                    \
         }
@@ -381,34 +438,27 @@ bool Foam::expressions::exprValue::readTokens(ITstream& is)
 
     const valueTypeCode whichCode(exprValue::peekType(is));
 
-    if (whichCode == expressions::valueTypeCode::NONE)
-    {
-        typeCode_ = whichCode;
-        is.skip(2);  // Skip tokens: '( )'
-        return true;
-    }
-
-    // This one should be rare or even impossible
-    if (whichCode == expressions::valueTypeCode::INVALID)
+    switch (whichCode)
     {
-        typeCode_ = whichCode;
-
-        if (is.bad())
+        case expressions::valueTypeCode::NONE :
         {
-            return false;
+            typeCode_ = whichCode;
+            is.skip(2);  // Skip tokens: '( )'
+            return true;
         }
 
-        const token& tok0 = is.peek();
-
-        if (tok0.isWord("bad"))
+        // This one should be rare or even impossible
+        case expressions::valueTypeCode::INVALID :
         {
-            is.skip(1);  // Skip token: "bad"
-            return true;
+            typeCode_ = whichCode;
+            if (!is.bad() && is.peek().isWord("bad"))
+            {
+                is.skip(1);  // Skip token: "bad"
+                return true;
+            }
+            return false;  // Some type of failure..
         }
-    }
 
-    switch (whichCode)
-    {
         #undef  doLocalCode
         #define doLocalCode(Type, UnusedParam)                        \
                                                                       \
@@ -430,6 +480,58 @@ bool Foam::expressions::exprValue::readTokens(ITstream& is)
 }
 
 
+int Foam::expressions::exprValue::compare
+(
+    const exprValue& rhs
+) const
+{
+    if (typeCode_ != rhs.typeCode_)
+    {
+        // First compare by type
+        return (int(typeCode_) - int(rhs.typeCode_));
+    }
+    else if ((this == &rhs) || !good())
+    {
+        // Identical: same object or not good
+        // (ie, no further comparison possible)
+        return 0;
+    }
+
+    // Types are identical (and good)
+    // - compare by value.
+    // This is even messier than usual, since can only rely on
+    // operator< being defined
+
+    switch (typeCode_)
+    {
+        #undef  doLocalCode
+        #define doLocalCode(Type, UnusedParam)                        \
+                                                                      \
+        case expressions::valueTypeCode::type_##Type :                \
+        {                                                             \
+            const Type* a = data_.get<Type>();                        \
+            const Type* b = rhs.data_.get<Type>();                    \
+            return                                                    \
+            (                                                         \
+                (a && b)                                              \
+              ? ((*a < *b) ? -1 : (*b < *a) ? 1 : 0)                  \
+              : 0                                                     \
+            );                                                        \
+            break;                                                    \
+        }
+
+        FOR_ALL_EXPR_VALUE_TYPES(doLocalCode);
+        #undef doLocalCode
+
+        // exprValue may only be a subset of valueTypeCode types
+        default: break;
+    }
+
+    // Should not happen
+    return 0;
+}
+
+
 // * * * * * * * * * * * * * * * Member Operators  * * * * * * * * * * * * * //
 
 bool Foam::expressions::exprValue::operator==(const exprValue& rhs) const
@@ -470,8 +572,7 @@ bool Foam::expressions::exprValue::operator==(const exprValue& rhs) const
 
 bool Foam::expressions::exprValue::operator<(const exprValue& rhs) const
 {
-    // Not yet sortable
-    return false;
+    return (this->compare(rhs) < 0);
 }
 
 
diff --git a/src/OpenFOAM/expressions/value/exprValue.H b/src/OpenFOAM/expressions/value/exprValue.H
index 16cf5753c2310452b47a03c795fc50147da21cdf..97e057614dde8112ec700cf37e21310f953b29eb 100644
--- a/src/OpenFOAM/expressions/value/exprValue.H
+++ b/src/OpenFOAM/expressions/value/exprValue.H
@@ -5,7 +5,7 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2021-2023 OpenCFD Ltd.
+    Copyright (C) 2021-2024 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -42,10 +42,8 @@ SourceFiles
 #ifndef Foam_expressions_exprValue_H
 #define Foam_expressions_exprValue_H
 
-#include "exprTraits.H"
-#include "error.H"
+#include "exprTraits.H"  // For valueTypeCode and label, scalar, vector etc.
 #include "contiguous.H"
-#include "tokenList.H"
 #include "InfoProxy.H"
 #include <typeinfo>
 
@@ -102,12 +100,7 @@ union exprValueUnion
         //  This seems to be the best way to catch programming errors
         //  since static_assert does not help here.
         //  The 'noexcept' is slightly misleading (needed for the caller)
-        static void notSpecialized(const std::string& msg) noexcept
-        {
-            FatalErrorInFunction
-                << "non-specialized: " << msg.c_str() << endl
-                << abort(FatalError);
-        }
+        static void notSpecialized(const std::string& msg) noexcept;
 
         //- Return read pointer to typed union member,
         //- which is nullptr for unspecialized versions
@@ -175,13 +168,8 @@ class exprValue
         //- Copy assignment
         void deepCopy(const exprValue& rhs);
 
-
 public:
 
-    //- Runtime type information
-    ClassName("exprValue");
-
-
     // Constructors
 
         //- Default construct (zero-initialized) as 'none'
@@ -244,7 +232,7 @@ public:
         //- The value type code
         expressions::valueTypeCode typeCode() const noexcept
         {
-           return typeCode_;
+            return typeCode_;
         }
 
         //- True if the value type is not none/invalid
@@ -277,6 +265,9 @@ public:
         //  \return True on success
         bool readTokens(ITstream& is);
 
+        //- Compare (type,value)
+        int compare(const exprValue& rhs) const;
+
 
     // Typed Access
 
@@ -350,7 +341,7 @@ public:
         //- Compare (type,value) for inequality
         bool operator!=(const exprValue& rhs) const { return !(*this == rhs); }
 
-        //- Compare (type,value) - currently not implemented.
+        //- Compare (type,value)
         bool operator<(const exprValue& rhs) const;
 
 
@@ -359,17 +350,12 @@ public:
         //- Return info proxy for printing information to a stream
         InfoProxy<exprValue> info() const { return *this; }
 
-        //- The exprValue as tokens.
-        //  For none : emits pair of brackets.
-        //  For invalid : emits "bad".
-        //
-        //  \param prune suppress the output for none/invalid
-        tokenList tokens(bool prune = false) const;
-
         //- Write the (type-specific) content.
         //  For none : emits pair of brackets.
         //  For invalid : emits "bad".
         //
+        //  Use OTstream for the stream to recover as tokens.
+        //
         //  \param prune suppress the output for none/invalid
         void write(Ostream& os, bool prune = false) const;
 };
diff --git a/src/OpenFOAM/expressions/value/exprValueFieldTag.C b/src/OpenFOAM/expressions/value/exprValueFieldTag.C
new file mode 100644
index 0000000000000000000000000000000000000000..0e739b8a869e7db6fd82dfe4146fb9f14571ced2
--- /dev/null
+++ b/src/OpenFOAM/expressions/value/exprValueFieldTag.C
@@ -0,0 +1,307 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2023-2024 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+License
+    This file is part of OpenFOAM.
+
+    OpenFOAM is free software: you can redistribute it and/or modify it
+    under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+    for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
+
+\*---------------------------------------------------------------------------*/
+
+#include "exprValueFieldTag.H"
+#include "PstreamReduceOps.H"
+
+// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
+
+bool Foam::expressions::exprValueFieldTag::empty() const noexcept
+{
+    return
+    (
+        uniformity_ == Foam::Detail::ListPolicy::uniformity::EMPTY
+    );
+}
+
+
+bool Foam::expressions::exprValueFieldTag::is_uniform() const noexcept
+{
+    return
+    (
+        uniformity_ == Foam::Detail::ListPolicy::uniformity::UNIFORM
+    );
+}
+
+
+bool Foam::expressions::exprValueFieldTag::is_nonuniform() const noexcept
+{
+    return
+    (
+        uniformity_ == Foam::Detail::ListPolicy::uniformity::NONUNIFORM
+        // Extra safety for direct reductions?
+        // || uniformity_ == Foam::Detail::ListPolicy::uniformity::MIXED
+    );
+}
+
+
+const Foam::expressions::exprValue&
+Foam::expressions::exprValueFieldTag::value() const noexcept
+{
+    return value_;
+}
+
+
+void Foam::expressions::exprValueFieldTag::set_empty()
+{
+    uniformity_ = Foam::Detail::ListPolicy::uniformity::EMPTY;
+    value_ = Foam::zero{};
+}
+
+
+void Foam::expressions::exprValueFieldTag::set_nouniform()
+{
+    uniformity_ = Foam::Detail::ListPolicy::uniformity::NONUNIFORM;
+    value_ = Foam::zero{};
+}
+
+
+int Foam::expressions::exprValueFieldTag::compare
+(
+    const exprValueFieldTag& rhs
+) const
+{
+    if (uniformity_ != rhs.uniformity_)
+    {
+        // First compare by uniformity
+        return (int(uniformity_) - int(rhs.uniformity_));
+    }
+    if (this == &rhs)
+    {
+        // Identical objects
+        return 0;
+    }
+
+    return value_.compare(rhs.value_);
+}
+
+
+bool Foam::expressions::exprValueFieldTag::equal
+(
+    const exprValueFieldTag& rhs
+) const
+{
+    return (value_ == rhs.value_);
+}
+
+
+void Foam::expressions::exprValueFieldTag::reduce()
+{
+    if (!UPstream::is_parallel())
+    {
+        // Nothing to do
+        return;
+    }
+
+    // Two-stage reduction
+    // ~~~~~~~~~~~~~~~~~~~
+    //
+    // Fields will usually be non-uniform somewhere, so first check with
+    // the cheapest option (bit-wise Allreduce).
+    // Only if they are uniform (with/without empty) do we actually
+    // need to compare values.
+
+    typedef unsigned char bitmask_type;
+
+    bitmask_type shape = static_cast<bitmask_type>(uniformity_);
+
+    // Step 1
+    // ~~~~~~
+    Foam::reduce
+    (
+        shape,
+        bitOrOp<bitmask_type>{},
+        UPstream::msgType(),  // ignored
+        UPstream::worldComm
+    );
+
+    // Step 2
+    // ~~~~~~
+    if
+    (
+        shape == static_cast<bitmask_type>
+        (
+            Foam::Detail::ListPolicy::uniformity::EMPTY
+        )
+    )
+    {
+        // no-op (empty everywhere)
+        value_ = Foam::zero{};
+    }
+    else if
+    (
+        shape == static_cast<bitmask_type>
+        (
+            Foam::Detail::ListPolicy::uniformity::UNIFORM
+        )
+    )
+    {
+        // Ranks are locally uniform (or empty), need to check values too
+        Foam::reduce
+        (
+            *this,
+            exprValueFieldTag::combineOp{},
+            UPstream::msgType(),
+            UPstream::worldComm
+        );
+    }
+    else
+    {
+        // Field is global non-empty and not uniform
+        set_nouniform();
+    }
+}
+
+
+Foam::expressions::exprValueFieldTag
+Foam::expressions::exprValueFieldTag::returnReduce
+(
+    const exprValueFieldTag& tag
+)
+{
+    exprValueFieldTag work(tag);
+    work.reduce();
+    return work;
+}
+
+
+void Foam::expressions::exprValueFieldTag::combine
+(
+    const exprValueFieldTag& b
+)
+{
+    exprValueFieldTag& a = *this;
+
+    if (b.empty())
+    {
+        // no-op
+        return;
+    }
+    else if (a.empty())
+    {
+        a = b;
+    }
+    else if (a.is_uniform() && (!b.is_uniform() || !a.equal(b)))
+    {
+        // Handle two cases:
+        // 1. uniform / non-uniform
+        // 2. uniform / uniform, but with different values
+        a.set_nouniform();
+    }
+
+    // No meaningful value if it is not uniform.
+    // So use zero, but keep the type
+    if (!a.is_uniform())
+    {
+        a.value_ = Foam::zero{};
+    }
+}
+
+
+// * * * * * * * * * * * * * * * Member Operators  * * * * * * * * * * * * * //
+
+bool Foam::expressions::exprValueFieldTag::
+operator==(const exprValueFieldTag& rhs) const
+{
+    if (uniformity_ != rhs.uniformity_)
+    {
+        // Uniformity must match
+        return false;
+    }
+    else if (this == &rhs)
+    {
+        return true;
+    }
+
+    return (value_ == rhs.value_);
+}
+
+
+bool Foam::expressions::exprValueFieldTag::
+operator<(const exprValueFieldTag& rhs) const
+{
+    return (this->compare(rhs) < 0);
+}
+
+
+// * * * * * * * * * * * * * * * IOstream Operators  * * * * * * * * * * * * //
+
+void Foam::expressions::exprValueFieldTag::read(Istream& is)
+{
+    label uniformTag;
+
+    is.readBegin("fieldTag");
+
+    is >> uniformTag;
+    uniformity_ = int(uniformTag);
+    value_.read(is);
+
+    is.readEnd("fieldTag");
+}
+
+
+void Foam::expressions::exprValueFieldTag::write(Ostream& os) const
+{
+    os  << token::BEGIN_LIST
+        << label(uniformity_) << token::SPACE;
+    value_.write(os, false);  // No pruning
+    os  << token::END_LIST;
+}
+
+
+void Foam::expressions::exprValueFieldTag::print(Ostream& os) const
+{
+    os  << "{ uniform:"
+        << label(uniformity_)
+        << " type:" << label(value_.typeCode())
+        << " value: " << value_ << " }";
+}
+
+
+Foam::Istream& Foam::operator>>
+(
+    Istream& is,
+    expressions::exprValueFieldTag& tag
+)
+{
+    tag.read(is);
+    return is;
+}
+
+
+Foam::Ostream& Foam::operator<<
+(
+    Ostream& os,
+    const expressions::exprValueFieldTag& tag
+)
+{
+    tag.write(os);
+    return os;
+}
+
+
+// ************************************************************************* //
diff --git a/applications/test/exprValue2/exprValueFieldTag.H b/src/OpenFOAM/expressions/value/exprValueFieldTag.H
similarity index 58%
rename from applications/test/exprValue2/exprValueFieldTag.H
rename to src/OpenFOAM/expressions/value/exprValueFieldTag.H
index cd92e3597cbf878d79f193514c66c893b3f5e9ff..25f9e29f67bb33e095f8d6e689d24ce40c7231b6 100644
--- a/applications/test/exprValue2/exprValueFieldTag.H
+++ b/src/OpenFOAM/expressions/value/exprValueFieldTag.H
@@ -5,7 +5,7 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2023 OpenCFD Ltd.
+    Copyright (C) 2023-2024 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -27,11 +27,11 @@ Class
     Foam::expressions::exprValueFieldTag
 
 Description
-    A polymorphic single-value container for tracking Field content
-    as uniform etc.
+    An expressions::exprValue (polymorphic typed union) with an additional
+    flag for tracking Field content as uniform etc.
 
 SourceFiles
-    exprValueFieldTag.cxx
+    exprValueFieldTag.C
 
 \*---------------------------------------------------------------------------*/
 
@@ -39,7 +39,7 @@ SourceFiles
 #define Foam_expressions_exprValueFieldTag_H
 
 #include "exprValue.H"
-#include "List.H"  // For ListPolicy
+#include "UList.H"  // For ListPolicy
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
@@ -54,21 +54,39 @@ namespace expressions
 
 class exprValueFieldTag
 {
-    // Private data
+    // Private Data
 
-        //- Uniformity of field
+        //- Uniformity of field (0: empty, 1: uniform, 2: non-uniform, ...)
+        //  Values as per internal enum Foam::Detail::ListPolicy::uniformity
         int uniformity_{};
 
-        //- Representative (uniform) value for the field
+        //- Representative (uniform) type/value for the field
         expressions::exprValue value_{};
 
+
 public:
 
-    // Constructors
+    // Generated Methods
 
-        //- Default construct
+        //- Default construct. Uniformity = empty, type = none
         exprValueFieldTag() = default;
 
+        //- Copy construct
+        exprValueFieldTag(const exprValueFieldTag&) = default;
+
+        //- Copy assignment
+        exprValueFieldTag& operator=(const exprValueFieldTag&) = default;
+
+
+    // Constructors
+
+        //- Construct as uniform with the specified type/value
+        template<class Type>
+        explicit exprValueFieldTag(const Type& val)
+        {
+            set_value(val);
+        }
+
         //- Construct from a range of values
         template<class Type>
         explicit exprValueFieldTag(const Type* first, const Type* last)
@@ -77,6 +95,18 @@ public:
         }
 
 
+    // Factory Methods
+
+        //- Make an empty field tag with the specified type (zero-value)
+        template<class Type>
+        static exprValueFieldTag make_empty()
+        {
+            exprValueFieldTag tag;  // construct empty, no type
+            tag.set_zero<Type>();   // set type and zero value
+            return tag;
+        }
+
+
     // Member Functions
 
         //- True if the uniformity is "empty"
@@ -88,9 +118,18 @@ public:
         //- True if the uniformity is "non-uniform"
         bool is_nonuniform() const noexcept;
 
+        //- Representative (uniform) value for the field
+        const expressions::exprValue& value() const noexcept;
+
+        //- Compare (uniformity, type, value)
+        int compare(const exprValueFieldTag& rhs) const;
+
         //- Test for equality of the values
         bool equal(const exprValueFieldTag& rhs) const;
 
+
+    // Setters
+
         //- Set value and uniformity from range of data
         template<class Type>
         void set(const Type* first, const Type* last)
@@ -104,29 +143,48 @@ public:
             }
             else
             {
+                // Is empty, set zero value
                 value_.set<Type>(Foam::zero{});
             }
         }
 
-        //- Set uniform type and value
+        //- Set as uniform, with specified value
         template<class Type>
-        void set_uniform(const Type& val)
+        void set_value(const Type& val)
         {
             uniformity_ = Foam::Detail::ListPolicy::uniformity::UNIFORM;
             value_.set<Type>(val);
         }
 
-        //- Set as non-uniform
-        void set_nouniform() noexcept;
+        //- Set type and zero value (does not affect uniformity)
+        template<class Type>
+        void set_zero()
+        {
+            value_.set<Type>(Foam::zero{});
+        }
+
+        //- Set as empty with zero value, leave type unchanged
+        void set_empty();
+
+        //- Set as non-uniform with zero value, leave type unchanged
+        void set_nouniform();
+
+
+    // Parallel
+
+        //- Inplace parallel reduction, uses worldComm
+        void reduce();
+
+        //- Perform a reduction on a copy and return the result
+        static exprValueFieldTag returnReduce(const exprValueFieldTag& tag);
 
 
     // Reduction operations
 
-        //- Combine - eg, for global uniformity
+        //- Inplace combine - eg, for global uniformity
         void combine(const exprValueFieldTag& b);
 
-        //- Binary operator to be used by reduce function for detecting
-        //- global uniformity
+        //- Binary combine operator, e.g. for global reduction
         struct combineOp
         {
             exprValueFieldTag operator()
@@ -144,11 +202,32 @@ public:
 
     // IO Operations
 
+        //- Read uniformity label and the value as pair
         void read(Istream& is);
+
+        //- Write uniformity label and the value as pair
         void write(Ostream& os) const;
 
         //- Print description to Ostream
         void print(Ostream& os) const;
+
+
+    // Member Operators
+
+        //- Assign from zero. Changes value but not type
+        void operator=(const Foam::zero) { value_ = Foam::zero{}; }
+
+        //- Compare (uniformity,value) for equality
+        bool operator==(const exprValueFieldTag&) const;
+
+        //- Compare (uniformity,value) for inequality
+        bool operator!=(const exprValueFieldTag& rhs) const
+        {
+            return !(*this == rhs);
+        }
+
+        //- Compare (uniformity,value)
+        bool operator<(const exprValueFieldTag&) const;
 };
 
 } // End namespace expressions
diff --git a/src/OpenFOAM/expressions/value/exprValueFwd.H b/src/OpenFOAM/expressions/value/exprValueFwd.H
new file mode 100644
index 0000000000000000000000000000000000000000..f352103d99ddda776ec7d0fb18a868f2e0a3138c
--- /dev/null
+++ b/src/OpenFOAM/expressions/value/exprValueFwd.H
@@ -0,0 +1,56 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2024 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+License
+    This file is part of OpenFOAM.
+
+    OpenFOAM is free software: you can redistribute it and/or modify it
+    under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+    for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
+
+Class
+    Foam::expressions::exprValue
+
+Description
+    Forward declarations for Foam::expressions::exprValue etc
+
+\*---------------------------------------------------------------------------*/
+
+#ifndef Foam_expressions_exprValueFwd_H
+#define Foam_expressions_exprValueFwd_H
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+namespace expressions
+{
+
+// Forward Declarations
+class exprValue;
+class exprValueFieldTag;
+
+} // End namespace expressions
+} // End namespace Foam
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#endif
+
+// ************************************************************************* //