Skip to content

Commit fd60b20

Browse files
authored
fix(__any_allocator): add friend declaration so the cross-specialization converting constructor compiles (NVIDIA#2159)
* fix(__any_allocator): add friend declaration so the cross-specialization converting constructor compiles on MSVC __any_allocator's converting constructor reads the private member of another specialization of the same class template, which the standard permits ([class.access], since C++17) but MSVC rejects with C2248. The conversion is instantiated whenever task_scheduler's type-erased backend copies an allocator on the heap-allocation fallback path (e.g. with an asio-based scheduler), so stdexec::task + asio + MSVC currently fails to compile. Add an explicit friend declaration (harmless on GCC/Clang) and a regression test that instantiates the converting constructor directly. Fixes NVIDIA#2158 * docs: correct the rationale — the cross-specialization access is rejected by GCC, Clang and MSVC alike The converting constructor of __any_allocator reads the private member of another specialization of the same template. That is rejected by all major compilers (MSVC C2248; Clang/GCC 'private member' error); upstream never noticed because the constructor is only instantiated on the heap-allocation fallback path of task_scheduler (large operation states, e.g. asio-based schedulers), which the upstream test matrix does not exercise. The friend declaration makes the conversion legal on every compiler. * style: fix clang-format violations in test_any_allocator.cpp
1 parent 17bf2c4 commit fd60b20

3 files changed

Lines changed: 47 additions & 0 deletions

File tree

include/stdexec/__detail/__any_allocator.hpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,12 @@ namespace STDEXEC
6767

6868
__any_allocator() = default;
6969

70+
// The converting constructor below accesses the private member of another
71+
// specialization of this template, which is rejected by MSVC, Clang and
72+
// GCC. Declare the friendship explicitly.
73+
template <class>
74+
friend struct __any_allocator;
75+
7076
template <__not_same_as<__any_allocator> _Alloc>
7177
requires __is_not_instance_of<_Alloc, __any_allocator> && __simple_allocator<_Alloc>
7278
__any_allocator(_Alloc __alloc) noexcept

test/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ set(stdexec_test_sources
7575
stdexec/algos/consumers/test_sync_wait.cpp
7676
stdexec/algos/consumers/test_spawn.cpp
7777
stdexec/detail/test_any.cpp
78+
stdexec/detail/test_any_allocator.cpp
7879
stdexec/detail/test_common_domain.cpp
7980
stdexec/detail/test_completion_signatures.cpp
8081
stdexec/detail/test_demangle.cpp
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
3+
* SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
4+
*
5+
* Licensed under the Apache License, Version 2.0 with LLVM Exceptions (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* https://llvm.org/LICENSE.txt
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
#include <stdexec/execution.hpp>
19+
20+
#include <test_common/catch2.hpp>
21+
22+
#include <utility>
23+
24+
// The converting constructor __any_allocator(_Uy) reads the private member of
25+
// another specialization of the same class template. That access is rejected by
26+
// all major compilers (MSVC C2248, Clang and GCC report it as accessing a
27+
// private member), so the constructor only compiles because it is never
28+
// instantiated in the upstream test matrix. It *is* instantiated whenever
29+
// task_scheduler's type-erased backend copies an allocator on the
30+
// heap-allocation fallback path (e.g. with an asio-based scheduler), breaking
31+
// the build on every compiler; the friend declaration makes the conversion
32+
// legal. This test instantiates the constructor directly and guards the fix
33+
// for NVIDIA/stdexec#2158.
34+
TEST_CASE("__any_allocator cross-specialization converting constructor compiles",
35+
"[detail][allocator]")
36+
{
37+
STDEXEC::__any_allocator<int> src;
38+
STDEXEC::__any_allocator<std::byte> dst(std::move(src)); // instantiates the converting ctor
39+
CHECK(dst.has_value() == false);
40+
}

0 commit comments

Comments
 (0)