diff --git a/CHANGELOG.md b/CHANGELOG.md
index 26c7615cc15b0d647e82c76028966c01ce03d82f..974275e5c8b0b08231cc150eb116e39ccd18c739 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -53,6 +53,11 @@ Coq 8.11 is no longer supported.
   lemmas using `Qp.lem` instead of `Qp_lem`.
 - Rename `_plus`/`_minus` into `_add`/`_sub` to be consistent with Coq's current
   convention for numbers. See the sed script below for an exact list of renames.
+- Refactor the `feed` and `efeed` tactics. In particular, improve the
+  documentation of `(e)feed` tactics, rename the primitive `(e)feed`
+  tactic to `(e)feed_core`, make the syntax of `feed_core` consistent
+  with `efeed_core`, remove the `by` parameter of `efeed_core`, and add
+  `(e)feed generalize`, `efeed inversion`, and `efeed destruct`.
 
 The following `sed` script should perform most of the renaming
 (on macOS, replace `sed` by `gsed`, installed via e.g. `brew install gnu-sed`).
diff --git a/stdpp/tactics.v b/stdpp/tactics.v
index d4c143a6faf0c86a147470ceaac4025c744c7d4e..1e322e13992d2906eb838ce75c202dbefaf5d875 100644
--- a/stdpp/tactics.v
+++ b/stdpp/tactics.v
@@ -496,10 +496,16 @@ Tactic Notation "iter" tactic(tac) tactic(l) :=
   let rec go l :=
   match l with ?x :: ?l => tac x || go l end in go l.
 
-(** Given [H : A_1 → ... → A_n → B] (where each [A_i] is non-dependent), the
-tactic [feed tac H tac_by] creates a subgoal for each [A_i] and calls [tac p]
-with the generated proof [p] of [B]. *)
-Tactic Notation "feed" tactic(tac) constr(H) :=
+(** Given [H : A_1 → ... → A_n → B] (where each [A_i] is non-dependent
+and [B] is not convertible to an arrow), the tactic [feed_core H using
+tac] creates a subgoal for each [A_i]  (in front of the original goal)
+and calls [tac p] with the generated proof [p] of [B].
+
+For example, given [H : P → Q → R], [feed_core H using (fun p
+=> pose proof p)] creates the subgoals [P] and [Q] in front of the
+original goal, and then in the original goal executes
+[pose proof Hfeed] where [Hfeed : R], which adds [R] to the context. *)
+Tactic Notation "feed_core" constr(H) "using" tactic3(tac) :=
   let rec go H :=
   let T := type of H in
   lazymatch eval hnf in T with
@@ -514,49 +520,97 @@ Tactic Notation "feed" tactic(tac) constr(H) :=
   | ?T1 => tac H
   end in go H.
 
-(** The tactic [efeed tac H] is similar to [feed], but it also instantiates
-dependent premises of [H] with evars. *)
-Tactic Notation "efeed" constr(H) "using" tactic3(tac) "by" tactic3 (bytac) :=
+(** The tactic [efeed_core H using tac] is similar to [feed_core], but
+it also instantiates dependent premises of [H] with evars.
+
+For example, given [H : ∀ x y, P x → Q y → R x y] (where [R x y] is
+not convertible to an arrow/forall), [efeed_core H using (fun p
+=> pose proof p)] creates evars [?x] and [?y] for [x] and [y] and
+subgoals [P ?x] and [Q ?y] in front of the original goal, and then
+in the original goal executes [pose proof Hfeed] where
+[Hfeed : R ?x ?y], which adds [R ?x ?y] to the context. *)
+Tactic Notation "efeed_core" constr(H) "using" tactic3(tac) :=
   let rec go H :=
   let T := type of H in
   lazymatch eval hnf in T with
   | ?T1 → ?T2 =>
     let HT1 := fresh "feed" in assert T1 as HT1;
-      [bytac | go (H HT1); clear HT1 ]
+      [| go (H HT1); clear HT1 ]
   | ?T1 → _ =>
     let e := mk_evar T1 in
     go (H e)
   | ?T1 => tac H
   end in go H.
-Tactic Notation "efeed" constr(H) "using" tactic3(tac) :=
-  efeed H using tac by idtac.
 
 (** The following variants of [pose proof], [specialize], [inversion], and
-[destruct], use the [feed] tactic before invoking the actual tactic. *)
+[destruct], use the [(e)feed_core] tactic before invoking the actual tactic. *)
+(** [feed pose proof H as H'] on [H : P → Q → R] creates two new
+subgoals [P] and [Q] in front of the original goal and adds [H' : R] to
+the context of the original goal. *)
 Tactic Notation "feed" "pose" "proof" constr(H) "as" ident(H') :=
-  feed (fun p => pose proof p as H') H.
+  feed_core H using (fun p => pose proof p as H').
 Tactic Notation "feed" "pose" "proof" constr(H) :=
-  feed (fun p => pose proof p) H.
+  feed_core H using (fun p => pose proof p).
 
 Tactic Notation "efeed" "pose" "proof" constr(H) "as" ident(H') :=
-  efeed H using (fun p => pose proof p as H').
+  efeed_core H using (fun p => pose proof p as H').
 Tactic Notation "efeed" "pose" "proof" constr(H) :=
-  efeed H using (fun p => pose proof p).
+  efeed_core H using (fun p => pose proof p).
+
+(** [feed specialize H] on hypothesis [H : P → Q → R] creates two new
+subgoals [P] and [Q] in front of the original goal and changes [H] to have
+type [R] in the original goal.
 
+Note that [(e)feed specialize] only works on hypotheses. For arbitrary
+proof terms, use [(e)feed pose proof]. *)
 Tactic Notation "feed" "specialize" hyp(H) :=
-  feed (fun p => specialize p) H.
+  feed_core H using (fun p => specialize p).
 Tactic Notation "efeed" "specialize" hyp(H) :=
-  efeed H using (fun p => specialize p).
+  efeed_core H using (fun p => specialize p).
+
+(** [feed generalize H] on [H : P → Q → R] creates two new
+subgoals [P] and [Q] in front of the original goal and adds [R] to
+the original goal (i.e. [Goal] becomes [R → Goal]).
+
+[efeed generalize] is also sometimes called [exploit] (e.g. in
+CompCert). *)
+Tactic Notation "feed" "generalize" constr(H) :=
+  feed_core H using (fun p => let H':=fresh in pose proof p as H'; revert H').
+Tactic Notation "efeed" "generalize" constr(H) :=
+  efeed_core H using (fun p => let H':=fresh in pose proof p as H'; revert H').
 
+(** [feed inversion H] on [H : P → Q → R] creates two new subgoals [P]
+and [Q] in front of the original goal and performs [inversion] on [R]
+in the original goal.
+
+Note that [(e)feed inversion] allows passing terms, not just
+identifiers (unlike standard [inversion]). *)
 Tactic Notation "feed" "inversion" constr(H) :=
-  feed (fun p => let H':=fresh in pose proof p as H'; inversion H') H.
+  feed_core H using (fun p => let H':=fresh in pose proof p as H'; inversion H').
+Tactic Notation "efeed" "inversion" constr(H) :=
+  efeed_core H using (fun p => let H':=fresh in pose proof p as H'; inversion H').
+
 Tactic Notation "feed" "inversion" constr(H) "as" simple_intropattern(IP) :=
-  feed (fun p => let H':=fresh in pose proof p as H'; inversion H' as IP) H.
+  feed_core H using (fun p => let H':=fresh in pose proof p as H'; inversion H' as IP).
+Tactic Notation "efeed" "inversion" constr(H) "as" simple_intropattern(IP) :=
+  efeed_core H using (fun p => let H':=fresh in pose proof p as H'; inversion H' as IP).
 
+(** [feed destruct H] on [H : P → Q → R] creates two new subgoals [P]
+and [Q] in front of the original goal and performs [destruct] on [R]
+in the original goal.
+
+[destruct] and [edestruct] already provide the functionality of
+[feed destruct] and [efeed destruct], but we provide [feed destruct]
+and [efeed destruct] for consistency. *)
 Tactic Notation "feed" "destruct" constr(H) :=
-  feed (fun p => let H':=fresh in pose proof p as H'; destruct H') H.
+  feed_core H using (fun p => let H':=fresh in pose proof p as H'; destruct H').
+Tactic Notation "efeed" "destruct" constr(H) :=
+  efeed_core H using (fun p => let H':=fresh in pose proof p as H'; destruct H').
+
 Tactic Notation "feed" "destruct" constr(H) "as" simple_intropattern(IP) :=
-  feed (fun p => let H':=fresh in pose proof p as H'; destruct H' as IP) H.
+  feed_core H using (fun p => let H':=fresh in pose proof p as H'; destruct H' as IP).
+Tactic Notation "efeed" "destruct" constr(H) "as" simple_intropattern(IP) :=
+  efeed_core H using (fun p => let H':=fresh in pose proof p as H'; destruct H' as IP).
 
 (** The block definitions are taken from [Coq.Program.Equality] and can be used
 by tactics to separate their goal from hypotheses they generalize over. *)