Here you’re trying to produce different term-level code based on type-level information, so you need a typeclass; and you’re trying to match on types, so you can use an associated type family. This method requires overlapping instances:
{-# Language
    AllowAmbiguousTypes,
    FlexibleInstances,
    TypeFamilies #-}
-- The class of overloads of ‘f’.
class F a where
  type family T a   -- Or: ‘type T a’
  f :: T a -> T a
-- The “default” catch-all overload.
instance {-# Overlappable #-} F a where
  type instance T a = a  -- Or: ‘type T a = a’
  f x = x
-- More specific instance, selected when ‘a ~ Int’.
instance {-# Overlapping #-} F Int where
  type instance T Int = Int
  f x = x + 1
Then f @Int 1 gives 2, and f 'c' gives 'c'.
But in this case, type family is unnecessary because it happens to be the identity—I include it for the sake of giving a point of generalisation. If you want to produce types by pattern-matching on types just like a function, then a closed type family is a great fit:
{-# Language
    KindSignatures,
    TypeFamilies #-}
import Data.Int
import Data.Word
import Data.Kind (Type)
type family Unsigned (a :: Type) :: Type where
  Unsigned Int   = Word
  Unsigned Int8  = Word8
  Unsigned Int16 = Word16
  Unsigned Int32 = Word32
  Unsigned Int64 = Word64
  Unsigned a     = a
Back to your question, here’s the original example with plain typeclasses:
class F a where
  f :: a -> a
instance {-# Overlappable #-} F a where
  f x = x
instance {-# Overlapping #-} F Int where
  f x = x + 1
Unfortunately, there’s no notion of a “closed typeclass” that would allow you to avoid the overlapping instances. In this case it’s fairly benign, but problems with coherence can arise with more complex cases, especially with MultiParamTypeClasses. In general it’s preferable to add a new method instead of writing overlapping instances when possible.
Note that in either case, f now has an F a constraint, e.g. the latter is (F a) => a -> a. You can’t avoid some change in the type; in Haskell, polymorphic means parametrically polymorphic, to preserve the property that types can be erased at compile time.
Other options include a GADT:
data FArg a where
  FInt :: Int -> FArg Int
  FAny :: a -> FArg a
f :: FArg a -> a
f (FInt x) = x + 1  -- Here we have ‘a ~ Int’ in scope.
f (FAny x) = x
Or (already mentioned in other answers) a Typeable constraint for dynamic typing:
{-# Language
    BlockArguments,
    ScopedTypeVariables #-}
import Data.Typeable (Typeable, eqT)
import Data.Type.Equality ((:~:)(Refl))
f :: forall a. (Typeable a) => a -> a
f x = fromMaybe x do
  Refl <- eqT @a @Int
  pure (x + 1)