`<ranges>`: _Zip_iterator_sentinel_equal redefinition error (C2382) when mixing #include <ranges> with import std;
Nobody has claimed this yet.
- 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
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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