Skip to content

Use std::invoke for uniform callable evaluation #56

Description

@ipapadop

Motivation

Several internal call paths use direct function-call syntax:

f(args...);

Direct invocation supports ordinary functions and function objects, but not the full standard INVOKE model. std::invoke uniformly supports:

  • function objects and lambdas;
  • function pointers;
  • pointers to member functions;
  • pointers to member data;
  • std::reference_wrapper targets.

Using it would make deferred callable interfaces align with standard C++ generic facilities such as std::invocable and std::invoke_result_t.

Proposed changes

Audit callable execution paths, especially:

  • detail::apply_impl;
  • detail::fun_ptr_wrapper::operator();
  • any direct calls used by evaluate, recursive_evaluate, or callable type deduction.

Replace direct calls with std::invoke where doing so expands conformance without changing intended recursive deferred semantics.

Example:

return std::invoke(
  std::forward<F>(f),
  std::get<I>(std::forward<Tuple>(t))()...);

Design considerations

  • Add <functional> only where directly required.
  • Preserve perfect forwarding and decltype(auto).
  • Propagate noexcept using the exact std::invoke expression.
  • Align constraints and traits with the invocation mechanism, using std::invocable or std::is_invocable_v consistently.
  • Test member-function pointers with object references, pointers, and std::reference_wrapper where supported by the surrounding deferred API.
  • Test member-data pointers and reference-preserving return behavior.
  • Determine whether fun_ptr_wrapper remains necessary after adopting std::invoke; remove it only if public behavior and type traits remain compatible.
  • Be cautious around recursive_evaluate: broadening what counts as invocable may intentionally change which objects are recursively evaluated.

Compatibility

For existing ordinary callables this should be behavior-preserving. Newly accepted member pointers are additive, but type and noexcept behavior should be verified across supported compilers.

Acceptance criteria

  • Deferred invocation uses std::invoke at the appropriate callable boundary.
  • Existing function, function-pointer, lambda, and function-object tests continue to pass.
  • Member-function pointers can be invoked with supported object/reference forms.
  • Member-data pointers preserve the expected reference/value category.
  • Perfect forwarding and decltype(auto) behavior are covered by compile-time tests.
  • noexcept reflects the underlying invocation.
  • Any retained function-pointer wrapper has a documented reason.
  • The full suite passes on GCC, Clang, and MSVC CI.

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