What you need is a tool that can parse C++, determine the meaning of its symbols, pick out boolean equations, and apply boolean simplification rules to them that don't violate the semantics.
A tool that can do this is our DMS Software Reengineering Toolkit with its C++ Front End. DMS is designed to carry out program analyses and source-to-source transformations on code. Using the C++ Front End, it can parse C++ to ASTs, build up symbol tables, and infer the type of an expression, and apply rewrite rules.
One can code rewrite rules like this:
domain Cpp. -- tell DMS to use the C++ front end
rule factor_common_and_term(e1: condition, e2:condition, e3: condition):
disjunctive_expression -> disjunctive_expression =
" \e1 && \e2 || \e1 && \e3 " -> " \e1 && ( \e2 || \e3 ) "
if no_side_effects(e1) /\ no_side_effects(e2);
to factor out a common condition. The rule has name "factor_common_and_term" to distinguish it from the often hundreds of other rules one might write (e.g., "distribute_term", etc.). The e1,e2,e3 are metavariables representing arbitrary subexpressions (of type "condition" according to the grammar rules). The rewrite operates only on disjunction_expressions; you could make this be "just expression" but then you would not get disjunctions nested inside other conditional expressions. The rewrite has a pattern (left) and a replacement (right), both wrapped in meta-quotes " to distinguish the C++ code in the patterns from the rule-language syntax surrounding it. The \e1 are escapes from C++ syntax, and indicate where a metavariable can match. Metavariables match any syntax of the corresponding category, so where \e1 is seen can be an arbitrarily complicated "condition". The fact that e1 is mentioned twice in the pattern forces the occurences to be identical.
One can write a set of rewrite rules that encode knowledge about simplifying arbitrarily complex boolean equations; a few dozen rules sort of does it. We've applied these to systems of non-C++ boolean equations with hundreds of thousands of terms, and to C and C++ prepreprocessor conditionals.
For C++, you need a check that the rewrite is safe to do, which it is not if e1 has a side effect, or e2 has a side effect. This check is made with an auxiliary function call that has to determine this answer in a conservative way. The determination that there is no side effect is in fact pretty complex for C++: you have to know what all the elements of the expression are, and that none of them have side effects.
One can do this check with DMS's attribute grammar (an organized tree crawl) that inspects all expression elements. Simple constants and variables (need a symbol table for this) do not. Function calls (including constructors, etc.) may; their definition has to be found (again the need for a symbol table), and processed similarly. It is possible that an expression element calls a separately compiled function; the conservative answer in this case is "don't know" therefore "assume has side effect". DMS can actually read multiple compilation units at the same time, so a separately compiled function can be found, parse/symbol-resolved, and crawled if you want to go that far.
So the boolean rewrite part is pretty easy; the side effect analysis is not.
We've used DMS to carry out massive changes on C++ code; we often cheat a bit by making assumptions about complex analyses like this. Usually we get suprised the same ways programmers get surprised ("What do you mean, that has a side effect?"). Mostly it works pretty well. We have done side-effect analysis in detail on C systems of 25 million lines; not quite there for C++ yet.
The side effect analysis only matters if some subexpression might be evaluated more than once.
OP's example, given in a comment, doesn't need them, and can be handled by the following rules:
rule not_on_disjunction(e1:condition, e2:condition):
condition -> condition =
" ! (\e1 || \e2) " -> " !\e1 && !\e2";
rule double_not(e:condition):
condition -> condition =
" ! ! \e " --> " \e ";
A complete, but simple worked example with more detailed description is this example of algebraic simplification of conventional algebra and some calculus.
There's clearly controversy as to whether a particular code transformation will make code more readable. IMHO, that's because the shape of code is often an art judgement, and we all seem to disagree about art. This isn't any different than letting somebody else modify your code.