Skip to content

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

@julixian

Description

@julixian

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingmodulesC++23 modules, C++20 header unitsrangesC++20/23 ranges

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions