microsoft / microsoft/STL

`<ranges>`: _Zip_iterator_sentinel_equal redefinition error (C2382) when mixing #include <ranges> with import std;

Open
#6,121 1 comment 2 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug modules ranges
Dominant language
C++
Stars
11.1k
Forks
1.7k
Avg merge
4d 15h
Merged PRs (30d)
22

Description

Describe the bug

When #include <ranges> is used together with import std; in the same translation
unit, MSVC emits C2382 ("redefinition; different exception specifications") on
the internal helper function std::ranges::_Zip_iterator_sentinel_equal
(defined around line 7144 of <ranges>).

Using either #include <ranges> alone or import std; alone compiles successfully.

Steps to reproduce

#include <ranges>

import std;

int main() {
    std::vector<int> a{1, 2, 3};
    std::vector<int> b{4, 5, 6};

    for (const auto& [x, y] : std::views::zip(a, b)) {
        std::println("{}, {}", x, y);
    }
}

Expected behavior

The program compiles and prints:

1, 4
2, 5
3, 6

Actual behavior

ranges(7144,31): error C2382: "std::ranges::_Zip_iterator_sentinel_equal":
    redefinition; different exception specifications

Analysis

The noexcept specification of _Zip_iterator_sentinel_equal contains a
complex fold expression:

noexcept((noexcept(_STD declval<const _LHSTupleTypes&>()
    == _STD declval<const _RHSTupleTypes&>()) && ...))

When the compiler encounters both a textually-included declaration (from
#include <ranges>) and a module-imported declaration (from import std;),
it must verify that the two declarations are equivalent. The equivalence
check appears to fail on this nested noexcept(fold-expression) — the
compiler builds two independent AST trees and cannot confirm they represent
the same exception specification.

The same noexcept expression also appears on the internal lambda inside
the function body(but this does not cause any error).

A way to fix

Extract the noexcept fold expression into a named variable template.
This way, the function signature only references a simple name + template
arguments, which the compiler can trivially match across the header and
module paths.

// Add before _Zip_iterator_sentinel_equal:

template <class _LHSTuple, class _RHSTuple>
inline constexpr bool _Zip_sentinel_equal_is_nothrow = false;

template <class... _LHSTupleTypes, class... _RHSTupleTypes>
inline constexpr bool _Zip_sentinel_equal_is_nothrow<
    tuple<_LHSTupleTypes...>, tuple<_RHSTupleTypes...>> =
    (noexcept(_STD declval<const _LHSTupleTypes&>()
        == _STD declval<const _RHSTupleTypes&>()) && ...);

// Then update the function:

template <class... _LHSTupleTypes, class... _RHSTupleTypes>
    requires (sizeof...(_LHSTupleTypes) == sizeof...(_RHSTupleTypes))
_NODISCARD constexpr bool _Zip_iterator_sentinel_equal(
    const tuple<_LHSTupleTypes...>& _Lhs_tuple,
    const tuple<_RHSTupleTypes...>& _Rhs_tuple)
    noexcept(_Zip_sentinel_equal_is_nothrow<
        tuple<_LHSTupleTypes...>, tuple<_RHSTupleTypes...>>) {

    const auto _Evaluate_equality_closure =
        [&_Lhs_tuple, &_Rhs_tuple]<size_t... _Indices>(
            index_sequence<_Indices...>)
            noexcept(_Zip_sentinel_equal_is_nothrow<
                tuple<_LHSTupleTypes...>, tuple<_RHSTupleTypes...>>) {
            return ((_STD get<_Indices>(_Lhs_tuple)
                == _STD get<_Indices>(_Rhs_tuple)) || ... || false);
        };

    return _Evaluate_equality_closure(
        index_sequence_for<_LHSTupleTypes...>{});
}

Additional

I understand that mixing #include and import std; is generally
discouraged. However, this scenario arises naturally when a third-party
library #includes standard headers internally, and the consuming
translation unit uses import std;(I think nobody wants to #define _RANGES_). It would be helpful if the STL
headers were resilient to this usage pattern where feasible.

STL version

  • MSVC Compiler version: 19.50.35725
  • _MSVC_STL_UPDATE: 202508

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Reproduce the issue with the provided translation unit using followed by import std;. Inspect around line 7144 and the _Zip_iterator_sentinel_equal noexcept specification, then apply and validate the proposed named variable-template approach. Done means the mixed include/import case compiles and prints the expected pairs, while each standalone form continues to compile.

Written by the indexing model from the issue text.

Assessment

Tech stack
cpp
Domain
compilers
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Stale
Clarity
Clearly specified
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.