diff --git a/System/OsString.hs b/System/OsString.hs
--- a/System/OsString.hs
+++ b/System/OsString.hs
@@ -124,6 +124,9 @@
   , count
   , findIndex
   , findIndices
+
+  -- * Coercions
+  , coercionToPlatformTypes
   )
 where
 
@@ -204,5 +207,5 @@
     , findIndices
     )
 import System.OsString.Internal.Types
-    ( OsString, OsChar )
+    ( OsString, OsChar, coercionToPlatformTypes )
 import Prelude ()
diff --git a/System/OsString/Internal.hs b/System/OsString/Internal.hs
--- a/System/OsString/Internal.hs
+++ b/System/OsString/Internal.hs
@@ -33,6 +33,7 @@
 #endif
 import GHC.Stack (HasCallStack)
 import Data.Coerce (coerce)
+import Data.Type.Coercion (coerceWith)
 
 
 
@@ -189,11 +190,9 @@
 
 -- | Converts back to a unicode codepoint (total).
 toChar :: OsChar -> Char
-#if defined(mingw32_HOST_OS) || defined(__MINGW32__)
-toChar (OsChar (WindowsChar w)) = chr $ fromIntegral w
-#else
-toChar (OsChar (PosixChar w)) = chr $ fromIntegral w
-#endif
+toChar = case coercionToPlatformTypes of
+  Left (co, _) -> chr . fromIntegral . getWindowsChar . coerceWith co
+  Right (co, _) -> chr . fromIntegral . getPosixChar . coerceWith co
 
 -- | /O(n)/ Append a byte to the end of a 'OsString'
 --
@@ -731,4 +730,3 @@
 -- @since 1.4.200.0
 findIndices :: (OsChar -> Bool) -> OsString -> [Int]
 findIndices = coerce PF.findIndices
-
diff --git a/System/OsString/Internal/Types.hs b/System/OsString/Internal/Types.hs
--- a/System/OsString/Internal/Types.hs
+++ b/System/OsString/Internal/Types.hs
@@ -26,12 +26,15 @@
   , PlatformChar
   , OsString(..)
   , OsChar(..)
+  , coercionToPlatformTypes
   )
 where
 
 
 import Control.DeepSeq
+import Data.Coerce (coerce)
 import Data.Data
+import Data.Type.Coercion (Coercion(..), coerceWith)
 import Data.Word
 import Language.Haskell.TH.Syntax
     ( Lift (..), lift )
@@ -178,47 +181,25 @@
 -- | \"String-Concatenation\" for 'OsString'. This is __not__ the same
 -- as '(</>)'.
 instance Monoid OsString where
-#if defined(mingw32_HOST_OS) || defined(__MINGW32__)
-    mempty      = OsString (WindowsString BS.empty)
-#if MIN_VERSION_base(4,16,0)
-    mappend = (<>)
-#else
-    mappend (OsString (WindowsString a)) (OsString (WindowsString b))
-      = OsString (WindowsString (mappend a b))
-#endif
-#else
-    mempty      = OsString (PosixString BS.empty)
-#if MIN_VERSION_base(4,16,0)
+    mempty  = coerce BS.empty
+#if MIN_VERSION_base(4,11,0)
     mappend = (<>)
 #else
-    mappend (OsString (PosixString a)) (OsString (PosixString b))
-      = OsString (PosixString (mappend a b))
-#endif
+    mappend = coerce (mappend :: BS.ShortByteString -> BS.ShortByteString -> BS.ShortByteString))
 #endif
+
 #if MIN_VERSION_base(4,11,0)
 instance Semigroup OsString where
-#if MIN_VERSION_base(4,16,0)
-#if defined(mingw32_HOST_OS) || defined(__MINGW32__)
-    (<>) (OsString (WindowsString a)) (OsString (WindowsString b))
-      = OsString (WindowsString (mappend a b))
-#else
-    (<>) (OsString (PosixString a)) (OsString (PosixString b))
-      = OsString (PosixString (mappend a b))
-#endif
-#else
-    (<>) = mappend
-#endif
+    (<>) = coerce (mappend :: BS.ShortByteString -> BS.ShortByteString -> BS.ShortByteString)
 #endif
 
 
 instance Lift OsString where
-#if defined(mingw32_HOST_OS) || defined(__MINGW32__)
-  lift (OsString (WindowsString bs))
-    = [| OsString (WindowsString (BS.pack $(lift $ BS.unpack bs))) :: OsString |]
-#else
-  lift (OsString (PosixString bs))
-    = [| OsString (PosixString (BS.pack $(lift $ BS.unpack bs))) :: OsString |]
-#endif
+  lift xs = case coercionToPlatformTypes of
+    Left (_, co) ->
+      [| OsString (WindowsString (BS.pack $(lift $ BS.unpack $ coerce $ coerceWith co xs))) :: OsString |]
+    Right (_, co) ->
+      [| OsString (PosixString (BS.pack $(lift $ BS.unpack $ coerce $ coerceWith co xs))) :: OsString |]
 #if MIN_VERSION_template_haskell(2,17,0)
   liftTyped = TH.unsafeCodeCoerce . TH.lift
 #elif MIN_VERSION_template_haskell(2,16,0)
@@ -244,3 +225,17 @@
 instance Ord OsChar where
   compare (OsChar a) (OsChar b) = compare a b
 
+-- | This is a type-level evidence that 'OsChar' is a newtype wrapper
+-- over 'WindowsChar' or 'PosixChar' and 'OsString' is a newtype wrapper
+-- over 'WindowsString' or 'PosixString'. If you pattern match on
+-- 'coercionToPlatformTypes', GHC will know that relevant types
+-- are coercible to each other. This helps to avoid CPP in certain scenarios.
+coercionToPlatformTypes
+  :: Either
+  (Coercion OsChar WindowsChar, Coercion OsString WindowsString)
+  (Coercion OsChar PosixChar, Coercion OsString PosixString)
+#if defined(mingw32_HOST_OS)
+coercionToPlatformTypes = Left (Coercion, Coercion)
+#else
+coercionToPlatformTypes = Right (Coercion, Coercion)
+#endif
diff --git a/bench/BenchOsString.hs b/bench/BenchOsString.hs
--- a/bench/BenchOsString.hs
+++ b/bench/BenchOsString.hs
@@ -14,6 +14,7 @@
 
 module BenchOsString (benchMark) where
 
+import           Data.Type.Coercion                    (coerceWith, sym)
 import           System.OsString                       (osstr)
 import qualified System.OsString                       as S
 import           System.OsString.Internal.Types        (OsString(..), OsChar(..), PosixChar(..), WindowsChar(..))
@@ -24,23 +25,20 @@
 benchStr = "OsString"
 
 w :: Int -> OsChar
-#if defined(mingw32_HOST_OS) || defined(__MINGW32__)
-w i = OsChar (WindowsChar (fromIntegral i))
-#else
-w i = OsChar (PosixChar (fromIntegral i))
-#endif
+w = case S.coercionToPlatformTypes of
+  Left (co, _) -> coerceWith (sym co) . WindowsChar . fromIntegral
+  Right (co, _) -> coerceWith (sym co) . PosixChar . fromIntegral
 
 hashWord8 :: OsChar -> OsChar
-#if defined(mingw32_HOST_OS) || defined(__MINGW32__)
-hashWord8 (OsChar (WindowsChar w)) = OsChar . WindowsChar . fromIntegral . hashInt . fromIntegral $ w
-#else
-hashWord8 (OsChar (PosixChar w)) = OsChar . PosixChar . fromIntegral . hashInt . fromIntegral $ w
-#endif
+hashWord8 = case S.coercionToPlatformTypes of
+  Left (co, _) ->
+    coerceWith (sym co) . WindowsChar . fromIntegral . hashInt . fromIntegral .
+      getWindowsChar . coerceWith co
+  Right (co, _) ->
+    coerceWith (sym co) . PosixChar . fromIntegral . hashInt . fromIntegral .
+      getPosixChar . coerceWith co
 
 iw :: OsChar -> Int
-#if defined(mingw32_HOST_OS) || defined(__MINGW32__)
-iw (OsChar (WindowsChar w)) = fromIntegral w
-#else
-iw (OsChar (PosixChar w)) = fromIntegral w
-#endif
-
+iw = case S.coercionToPlatformTypes of
+  Left (co, _) -> fromIntegral . getWindowsChar . coerceWith co
+  Right (co, _) -> fromIntegral . getPosixChar . coerceWith co
diff --git a/changelog.md b/changelog.md
--- a/changelog.md
+++ b/changelog.md
@@ -1,5 +1,9 @@
 # Changelog for [`os-string` package](http://hackage.haskell.org/package/os-string)
 
+## 2.0.2 *Dec 2023*
+
+* Implement coercionToPlatformTypes, fixes #4
+
 ## 2.0.1 *Dec 2023*
 
 * add `unsafeEncodeUtf`, fixes #5
diff --git a/os-string.cabal b/os-string.cabal
--- a/os-string.cabal
+++ b/os-string.cabal
@@ -1,6 +1,6 @@
 cabal-version:      2.2
 name:               os-string
-version:            2.0.1
+version:            2.0.2
 
 -- NOTE: Don't forget to update ./changelog.md
 license:            BSD-3-Clause
diff --git a/tests/bytestring-tests/Properties/Common.hs b/tests/bytestring-tests/Properties/Common.hs
--- a/tests/bytestring-tests/Properties/Common.hs
+++ b/tests/bytestring-tests/Properties/Common.hs
@@ -62,6 +62,8 @@
 import Data.Word
 
 import Control.Arrow
+import Data.Coerce (coerce)
+import Data.Type.Coercion (Coercion(..), coerceWith, sym)
 import Data.Foldable
 import Data.List as L
 import Data.Semigroup
@@ -145,28 +147,22 @@
 
 #ifdef OSWORD
 isSpace :: OsChar -> Bool
-#if defined(mingw32_HOST_OS) || defined(__MINGW32__)
-isSpace = isSpaceWin . getOsChar
-#else
-isSpace = isSpacePosix . getOsChar
-#endif
+isSpace = case OBS.coercionToPlatformTypes of
+  Left (co, _) -> isSpaceWin . coerceWith co
+  Right (co, _) -> isSpacePosix . coerceWith co
 
 numWord :: OsString -> Int
-#if defined(mingw32_HOST_OS) || defined(__MINGW32__)
-numWord = numWordWin . getOsString
-#else
-numWord = numWordPosix . getOsString
-#endif
+numWord = case OBS.coercionToPlatformTypes of
+  Left (_, co) -> numWordWin . coerceWith co
+  Right (_, co) -> numWordPosix . coerceWith co
 
 toElem :: OsChar -> OsChar
 toElem = id
 
 swapW :: OsChar -> OsChar
-#if defined(mingw32_HOST_OS) || defined(__MINGW32__)
-swapW = OsChar . swapWWin . getOsChar
-#else
-swapW = OsChar . swapWPosix . getOsChar
-#endif
+swapW = case OBS.coercionToPlatformTypes of
+  Left (co, _) -> coerceWith (sym co) . swapWWin . coerceWith co
+  Right (co, _) -> coerceWith (sym co) . swapWPosix . coerceWith co
 
 instance Arbitrary OsString where
   arbitrary = OsString <$> arbitrary
@@ -184,11 +180,9 @@
 deriving instance Bounded OsChar
 
 instance Arbitrary ShortByteString where
-#if defined(mingw32_HOST_OS) || defined(__MINGW32__)
-  arbitrary = getWindowsString <$> arbitrary
-#else
-  arbitrary = getPosixString <$> arbitrary
-#endif
+  arbitrary = case OBS.coercionToPlatformTypes of
+    Left (_, _) -> getWindowsString <$> arbitrary
+    Right (_, _) -> getPosixString <$> arbitrary
 
 #else
 
