Skip to content

fix: operator[]/operator/ SFINAE in trailing return type for MSVC C++20 (issue #515)#686

Merged
kris-jusiak merged 1 commit into
boost-ext:masterfrom
PavelGuzenfeld:fix/issue-515-msvc-cxx20-negated-guard
May 27, 2026
Merged

fix: operator[]/operator/ SFINAE in trailing return type for MSVC C++20 (issue #515)#686
kris-jusiak merged 1 commit into
boost-ext:masterfrom
PavelGuzenfeld:fix/issue-515-msvc-cxx20-negated-guard

Conversation

@PavelGuzenfeld
Copy link
Copy Markdown
Contributor

Problem

On MSVC C++20, writing event<e>[!guard] or *state[!guard] fails to compile with a template argument deduction error. The same pattern works on GCC, Clang, and MSVC C++17.

Root cause: front::event::operator[] and front::state_impl::operator[] (and the corresponding operator/ overloads) express their SFINAE constraint as a default template parameter:

template <class T, typename enable_if<callable<bool, T>::value, int>::type = 0>
constexpr auto operator[](const T &t) const { ... }

MSVC C++20 has a known issue where this form of SFINAE can fail to deduce T when the argument is a nested dependent type (here: front::not_<zero_wrapper<Guard>> produced by !guard). Specifically, MSVC's C++20 subscript-operator lookup interacts poorly with default-parameter SFINAE, causing it to reject the overload before deduction completes.

Fix

Move the enable_if constraint to the trailing return type, which is the standard and MSVC-compatible form of expression-SFINAE:

template <class T>
constexpr auto operator[](const T &t) const
    -> enable_if_t<callable<bool, T>::value, transition_eg<event, zero_wrapper<T>>> { ... }

This form of SFINAE has identical semantics — substitution failure removes the overload from the candidate set — but is evaluated strictly after deduction completes, which works correctly on all compilers.

Affected overloads

  • front::event::operator[]event<e>[guard] / event<e>[!guard]
  • front::event::operator/event<e>/action (same MSVC risk, fixed for consistency)
  • front::state_impl::operator[]*state[guard] / *state[!guard]
  • front::state_impl::operator/*state/action (same MSVC risk, fixed for consistency)

Test

Added negated_guard_compiles_on_event_and_state in test/ft/guards.cpp that exercises:

  • *state1 + event<event1>[!guard] (the primary reported failure)
  • state2[!guard] (the state-subscript form)

Both patterns were already rejected by MSVC C++20 before this fix.

Compatibility

All existing GCC 14 and Clang tests pass unchanged (C++17 and C++20). The trailing-return-type form is valid C++14 and up.

@PavelGuzenfeld PavelGuzenfeld force-pushed the fix/issue-515-msvc-cxx20-negated-guard branch 2 times, most recently from 9cfd37b to 1b4c7eb Compare May 27, 2026 05:21
…oost-ext#515)

MSVC C++20 fails to deduce T in front::event::operator[] and
front::state_impl::operator[] / operator/ when the SFINAE constraint
is expressed as a default template parameter:

  template <class T, typename enable_if<callable<bool,T>::value, int>::type = 0>
  constexpr auto operator[](const T &t) const { ... }

This pattern is known to cause template argument deduction failure in
MSVC C++20 when the argument type is a dependent nested type such as
front::not_<zero_wrapper<Guard>> (produced by !guard).

Fix: move the enable_if constraint to the trailing return type instead.
This form of SFINAE is handled correctly by all supported compilers
including MSVC C++17 and C++20:

  template <class T>
  constexpr auto operator[](const T &t) const
      -> enable_if_t<callable<bool,T>::value, transition_eg<...>> { ... }

Applied to all four overloads:
  - front::event::operator[]  (event<e>[guard])
  - front::event::operator/   (event<e>/action)
  - front::state_impl::operator[]  (*state[guard])
  - front::state_impl::operator/   (*state/action)

Test: guards.cpp::negated_guard_compiles_on_event_and_state
  Exercises event<e>[!guard] and *state[!guard] patterns that
  previously failed to compile on MSVC C++20.

Tested with:
  - GCC 14 / g++: C++17 and C++20 — all tests pass
  - macOS Clang: all tests pass (CI)
  - MSVC 19.51.36244 (VS 2022 17.x) via msvc-wine Docker:
      /std:c++17 — 30/33 pass (3 pre-existing C4458 shadow failures
                    in sm_impl::process_event, unrelated to this fix)
      /std:c++20 — 33/33 pass (all tests including negated_guard_*)
@PavelGuzenfeld PavelGuzenfeld force-pushed the fix/issue-515-msvc-cxx20-negated-guard branch from 1b4c7eb to d5a0f6a Compare May 27, 2026 10:13
@kris-jusiak kris-jusiak merged commit db96f89 into boost-ext:master May 27, 2026
5 checks passed
@PavelGuzenfeld PavelGuzenfeld deleted the fix/issue-515-msvc-cxx20-negated-guard branch May 27, 2026 13:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants