Skip to content

Preserve C++ short-circuit semantics for deferred logical operators #54

Description

@ipapadop

Problem

Deferred operator&& and operator|| currently use the generic invocation machinery:

return invoke(std::logical_and<>{}, lhs, rhs);

The generic expression evaluator evaluates all stored operands before calling the function object. As a result, the right operand is evaluated even when native C++ would short-circuit it.

The interface visually promises normal C++ behavior:

condition && side_effect();
condition || side_effect();

Eager evaluation can cause unexpected side effects, invalid accesses, or unnecessary work.

Expected behavior

  • lhs && rhs: evaluate rhs only if lhs evaluates to true.
  • lhs || rhs: evaluate rhs only if lhs evaluates to false.
  • Evaluate lhs exactly once.
  • Preserve deferred evaluation: neither operand is evaluated while constructing the expression.

Proposed implementation direction

Introduce dedicated logical expression nodes or dedicated lazy function objects that receive deferred operands rather than already-evaluated values. A generic expression_ using the current apply() path cannot provide short-circuiting because operand evaluation has already occurred.

The dedicated node should:

  1. evaluate the left subexpression;
  2. conditionally evaluate the right subexpression;
  3. return a result consistent with built-in logical operators;
  4. expose both operands through subexpression_types and visit().

Design considerations

  • Preserve contextual conversion to bool as closely as practical.
  • Match native evaluation order and evaluate each selected operand once.
  • Determine the result type and avoid returning operand types as built-in &&/|| return bool.
  • Maintain constexpr support.
  • Ensure const and non-const expressions behave consistently.
  • Consider whether any other operator currently appears to promise native sequencing semantics but uses eager generic evaluation.
  • Document that overloaded deferred operators build expressions immediately but execute with native logical short-circuit behavior later.

Acceptance criteria

  • Deferred && skips the right operand when the left operand is false.
  • Deferred || skips the right operand when the left operand is true.
  • The right operand is evaluated when required.
  • Each evaluated operand runs exactly once and in left-to-right order.
  • Construction evaluates neither operand.
  • Results have the expected Boolean type and value.
  • Const, mutable, and constexpr cases are tested.
  • Visitor traversal still exposes both unevaluated subexpressions.
  • Existing arithmetic and comparison expression behavior remains unchanged.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions