Skip to content

Commit eba5fec

Browse files
committed
feat(traits): 拆分 capturable_one 并补全 std::cref 支持
- 拆出单类型 capturable_one,capturable<Ts...> 改为对其做 && 折叠, 行为完全一致(空包仍为 true),所有现有调用点无需改动 - 补全 std::cref 只读捕获:新增运行时测试(验证绑定 const 引用、 可读不写回) ci: 新增 macOS workflow,修正误导性 badge,强制 clang-format 检查 - 新增 .github/workflows/macos.yml (macos-latest):debug-asan / debug-tsan / release,与 ubuntu.yml 的矩阵对齐;TSan job 沿用 ubuntu 的 suppressions 配置。补上"声称支持 macOS 但无 CI 覆盖"的缺口。 - README.md / README.en.md:将静态的"永远绿" img.shields.io 平台 badge 替换为各 workflow 的真实状态 badge (ubuntu.yml / windows.yml / macos.yml); 将 ci.yml 的 badge 重命名为 "Lint",名副其实反映它只跑 lint + install-consumer; 新增 CodeQL badge。C++23 / License / Header-Only 保留为静态描述。 - ubuntu.yml / windows.yml:新增 concurrency group 并启用 cancel-in-progress, 避免连续 push 时堆叠重复跑。 - ci.yml:让 clang-format 检查真正 gate —— 去掉 --Wno-error、加 --Werror, 使格式偏差直接让 job 失败,而非仅作提示。
1 parent face11c commit eba5fec

8 files changed

Lines changed: 66 additions & 60 deletions

File tree

.github/workflows/ci.yml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,11 @@ jobs:
2525
fetch-depth: 0
2626
- name: Install clang-format
2727
run: sudo apt-get install -y -qq clang-format-18
28-
- name: Run clang-format (changed files only, advisory)
28+
- name: Run clang-format (changed files, gating)
2929
run: |
3030
git diff origin/main --name-only --diff-filter=AM \
3131
'taskflowlite/**/*.hpp' 'taskflowlite/**/*.cpp' 'test/**/*.cpp' 'examples/**/*.cpp' | \
32-
xargs -r clang-format-18 --dry-run --Wno-error
33-
32+
xargs -r clang-format-18 --dry-run --Werror
3433
# clang-tidy (PR only)
3534
clang-tidy:
3635
name: clang-tidy

.github/workflows/macos.yml

Lines changed: 13 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -2,64 +2,44 @@ name: macOS
22

33
on: [push, pull_request]
44

5+
concurrency:
6+
group: ${{ github.workflow }}-${{ github.ref }}
7+
cancel-in-progress: true
8+
59
jobs:
610

711
# Debug + ASan/UBSan
812
debug-asan:
913
runs-on: macos-latest
1014
steps:
1115
- uses: actions/checkout@v4
12-
- name: Select latest Xcode (for C++20 <stop_token>)
13-
run: |
14-
LATEST=$(ls -d /Applications/Xcode_*.app 2>/dev/null | sort -Vr | head -1)
15-
if [ -n "$LATEST" ]; then
16-
sudo xcode-select -s "$LATEST"
17-
echo "Switched to $(xcode-select -p)"
18-
else
19-
echo "No Xcode found, using default: $(xcode-select -p)"
20-
fi
21-
clang++ --version
2216
- name: Configure
2317
run: cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug -DTFL_BUILD_TESTS=ON -DTFL_SANITIZER=ASAN
2418
- name: Build
25-
run: cmake --build build --parallel $(sysctl -n hw.logicalcpu)
19+
# macOS 无 nproc,用 sysctl 读核心数
20+
run: cmake --build build --parallel $(sysctl -n hw.ncpu)
2621
- name: Test
2722
working-directory: build
28-
# 单文件目标(tfl_test_*)默认 EXCLUDE_FROM_ALL,未被构建;用 -LE perfile 跳过它们
29-
# 只跑单体 TaskflowLiteTest 与按标签的 tfl.* 子测试(内容等价,无需重复)。
23+
# 单文件目标(tfl_test_*)默认 EXCLUDE_FROM_ALL,用 -LE perfile 跳过
24+
# 只跑单体 TaskflowLiteTest(已覆盖全部用例)。
3025
run: ctest --output-on-failure -LE perfile
3126

3227
# Debug + TSan
3328
debug-tsan:
3429
runs-on: macos-latest
3530
steps:
3631
- uses: actions/checkout@v4
37-
- name: Select latest Xcode (for C++20 <stop_token>)
38-
run: |
39-
LATEST=$(ls -d /Applications/Xcode_*.app 2>/dev/null | sort -Vr | head -1)
40-
if [ -n "$LATEST" ]; then
41-
sudo xcode-select -s "$LATEST"
42-
echo "Switched to $(xcode-select -p)"
43-
else
44-
echo "No Xcode found, using default: $(xcode-select -p)"
45-
fi
46-
clang++ --version
4732
- name: Configure
4833
run: cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug -DTFL_BUILD_TESTS=ON -DTFL_SANITIZER=TSAN
4934
- name: Build
50-
run: cmake --build build --parallel $(sysctl -n hw.logicalcpu)
35+
run: cmake --build build --parallel $(sysctl -n hw.ncpu)
5136
- name: Test
5237
working-directory: build
5338
run: |
5439
mkdir -p bin
55-
# suppressions: 压制非本库的已知 TSan 噪音(测试框架 / 标准库线程启动路径)。
56-
# - race:__thread_proxy 新版 libc++ std::thread 启动路径(thread.h __thread_proxy)
57-
# 被 TSan 标为 atomic-write vs read 的竞争, 实为标准库内部线程生命周期管理,
58-
# 非本库数据竞争; test_allocator.cpp 起多线程时触发, 无法也不应改标准库, 故压制。
59-
# - race:Catch::* Catch2 运行时的已知噪音。
60-
printf 'race:__thread_proxy\nrace:Catch::RunContext\nrace:Catch::Detail\nrace:__sanitizer::IsAccessibleMemoryRange\n' > bin/tsan_suppressions.txt
61-
# 用绝对路径: ctest 运行测试时工作目录是 build/bin, 相对路径会被叠加成 build/bin/bin/... 而读不到。
62-
# 显式指定 suppressions, 以本文件为准(覆盖环境里可能存在的其它 supp 来源)。
40+
# suppressions: 仅压制"非本库"的已知 sanitizer / 框架噪音,绝不盖本库竞争。
41+
# 本库代码栈顶是 tfl::xxx,匹配不上这些规则,真竞争仍会照常报出。
42+
printf 'race:__sanitizer::IsAccessibleMemoryRange\nrace:Catch::RunContext\nrace:Catch::Detail\n' > bin/tsan_suppressions.txt
6343
export TSAN_OPTIONS="suppressions=$(pwd)/bin/tsan_suppressions.txt halt_on_error=1 second_deadlock_stack=1 history_size=7"
6444
ctest --output-on-failure -LE perfile
6545
@@ -68,20 +48,10 @@ jobs:
6848
runs-on: macos-latest
6949
steps:
7050
- uses: actions/checkout@v4
71-
- name: Select latest Xcode (for C++20 <stop_token>)
72-
run: |
73-
LATEST=$(ls -d /Applications/Xcode_*.app 2>/dev/null | sort -Vr | head -1)
74-
if [ -n "$LATEST" ]; then
75-
sudo xcode-select -s "$LATEST"
76-
echo "Switched to $(xcode-select -p)"
77-
else
78-
echo "No Xcode found, using default: $(xcode-select -p)"
79-
fi
80-
clang++ --version
8151
- name: Configure
8252
run: cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DTFL_BUILD_TESTS=ON
8353
- name: Build
84-
run: cmake --build build --parallel $(sysctl -n hw.logicalcpu)
54+
run: cmake --build build --parallel $(sysctl -n hw.ncpu)
8555
- name: Test
8656
working-directory: build
8757
run: ctest --output-on-failure -LE perfile

.github/workflows/ubuntu.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ name: Ubuntu
22

33
on: [push, pull_request]
44

5+
concurrency:
6+
group: ${{ github.workflow }}-${{ github.ref }}
7+
cancel-in-progress: true
8+
59
jobs:
610

711
# Debug + ASan/UBSan

.github/workflows/windows.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ name: Windows
22

33
on: [push, pull_request]
44

5+
concurrency:
6+
group: ${{ github.workflow }}-${{ github.ref }}
7+
cancel-in-progress: true
8+
59
jobs:
610

711
# Debug + ASan

README.en.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
# TaskflowLite
22

3-
[![CI](https://github.com/wicyn/taskflowlite/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/wicyn/taskflowlite/actions/workflows/ci.yml)
4-
[![Linux](https://img.shields.io/badge/Linux-passing-success?logo=linux&logoColor=white)](https://github.com/wicyn/taskflowlite/actions/workflows/ci.yml)
5-
[![macOS](https://img.shields.io/badge/macOS-passing-success?logo=apple&logoColor=white)](https://github.com/wicyn/taskflowlite/actions/workflows/ci.yml)
6-
[![Windows](https://img.shields.io/badge/Windows-passing-success?logo=windows&logoColor=white)](https://github.com/wicyn/taskflowlite/actions/workflows/ci.yml)
3+
[![Ubuntu](https://github.com/wicyn/taskflowlite/actions/workflows/ubuntu.yml/badge.svg?branch=main)](https://github.com/wicyn/taskflowlite/actions/workflows/ubuntu.yml)
4+
[![Windows](https://github.com/wicyn/taskflowlite/actions/workflows/windows.yml/badge.svg?branch=main)](https://github.com/wicyn/taskflowlite/actions/workflows/windows.yml)
5+
[![macOS](https://github.com/wicyn/taskflowlite/actions/workflows/macos.yml/badge.svg?branch=main)](https://github.com/wicyn/taskflowlite/actions/workflows/macos.yml)
6+
[![CodeQL](https://github.com/wicyn/taskflowlite/actions/workflows/codeql-analysis.yml/badge.svg?branch=main)](https://github.com/wicyn/taskflowlite/actions/workflows/codeql-analysis.yml)
7+
[![Lint](https://github.com/wicyn/taskflowlite/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/wicyn/taskflowlite/actions/workflows/ci.yml)
78
[![C++23](https://img.shields.io/badge/C%2B%2B-23-blue?logo=cplusplus)](https://en.cppreference.com/w/cpp/23)
89
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
910
[![Header Only](https://img.shields.io/badge/Header--Only-Yes-success)](#)

README.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
# TaskflowLite
22

3-
[![CI](https://github.com/wicyn/taskflowlite/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/wicyn/taskflowlite/actions/workflows/ci.yml)
4-
[![Linux](https://img.shields.io/badge/Linux-passing-success?logo=linux&logoColor=white)](https://github.com/wicyn/taskflowlite/actions/workflows/ci.yml)
5-
[![macOS](https://img.shields.io/badge/macOS-passing-success?logo=apple&logoColor=white)](https://github.com/wicyn/taskflowlite/actions/workflows/ci.yml)
6-
[![Windows](https://img.shields.io/badge/Windows-passing-success?logo=windows&logoColor=white)](https://github.com/wicyn/taskflowlite/actions/workflows/ci.yml)
3+
[![Ubuntu](https://github.com/wicyn/taskflowlite/actions/workflows/ubuntu.yml/badge.svg?branch=main)](https://github.com/wicyn/taskflowlite/actions/workflows/ubuntu.yml)
4+
[![Windows](https://github.com/wicyn/taskflowlite/actions/workflows/windows.yml/badge.svg?branch=main)](https://github.com/wicyn/taskflowlite/actions/workflows/windows.yml)
5+
[![macOS](https://github.com/wicyn/taskflowlite/actions/workflows/macos.yml/badge.svg?branch=main)](https://github.com/wicyn/taskflowlite/actions/workflows/macos.yml)
6+
[![CodeQL](https://github.com/wicyn/taskflowlite/actions/workflows/codeql-analysis.yml/badge.svg?branch=main)](https://github.com/wicyn/taskflowlite/actions/workflows/codeql-analysis.yml)
7+
[![Lint](https://github.com/wicyn/taskflowlite/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/wicyn/taskflowlite/actions/workflows/ci.yml)
78
[![C++23](https://img.shields.io/badge/C%2B%2B-23-blue?logo=cplusplus)](https://en.cppreference.com/w/cpp/23)
89
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
910
[![Header Only](https://img.shields.io/badge/Header--Only-Yes-success)](#)

taskflowlite/core/traits.hpp

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -115,10 +115,24 @@ template <typename T>
115115
}
116116
} // namespace detail
117117

118+
/// @brief 单个类型能否被框架安全持久化存储(capturable 的逐参数原子)。
119+
///
120+
/// 满足以下任一条件即可:
121+
/// 1. 已被 std::ref/std::cref 显式包装(引用捕获);
122+
/// 2. 是右值且 decay 后可移动构造(接管临时对象所有权);
123+
/// 3. 是左值引用且 decay 后可拷贝构造(拷贝活对象)。
124+
///
125+
/// @note 这是 `capturable<Ts...>` 的单参数构件。单独暴露便于其它 concept
126+
/// 复用,也为将来的逐参数诊断留出接口。
127+
template <typename T>
128+
concept capturable_one =
129+
detail::is_reference_wrapper_after_decay_v<T> ||
130+
(!std::is_lvalue_reference_v<T> && std::is_move_constructible_v<std::decay_t<T>>) ||
131+
( std::is_lvalue_reference_v<T> && std::is_copy_constructible_v<std::decay_t<T>>);
118132

119133
/// @brief 可安全持久化存储的参数包约束。
120134
///
121-
/// 包中每个 T 满足以下任一条件:
135+
/// 包中每个 T 满足 `capturable_one<T>`:
122136
/// 1. 已被 std::ref/std::cref 显式包装(引用捕获)
123137
/// 2. 是右值且 decay 后可移动构造(接管临时对象所有权)
124138
/// 3. 是左值引用且 decay 后可拷贝构造(拷贝活对象)
@@ -130,12 +144,10 @@ template <typename T>
130144
/// - `capturable<std::string&&>` → true (情形 2)
131145
/// - `capturable<std::ref_wrapper<int>>`→ true (情形 1)
132146
/// - `capturable<std::unique_ptr<int>&>`→ false (不可拷贝构造)
147+
///
148+
/// @note 行为与拆分前完全一致(对 capturable_one 的 && 折叠)。
133149
template <typename... Ts>
134-
concept capturable = ((
135-
detail::is_reference_wrapper_after_decay_v<Ts> ||
136-
(!std::is_lvalue_reference_v<Ts> && std::is_move_constructible_v<std::decay_t<Ts>>) ||
137-
( std::is_lvalue_reference_v<Ts> && std::is_copy_constructible_v<std::decay_t<Ts>>)
138-
) && ...);
150+
concept capturable = (capturable_one<Ts> && ...);
139151

140152
/// @brief 检查是否为有效的谓词类型
141153
template <typename P, typename... Args>

test/test_flow.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,3 +246,18 @@ TEST_CASE("Flow: dump outputs D2 text", "[flow][dump]") {
246246
REQUIRE(out.find("A") != std::string::npos);
247247
REQUIRE(out.find("B") != std::string::npos);
248248
}
249+
250+
/// @section std-cref-readonly —— std::cref 只读引用:可读外部,不可写回
251+
TEST_CASE("std::cref read-only reference binds to const") {
252+
TestEnv env;
253+
tfl::Flow flow;
254+
int outside = 7;
255+
int seen = 0;
256+
flow.emplace([&seen](const int& r) {
257+
seen = r; // 能读到外部当前值
258+
// r = 0; // 若取消注释应编译失败:const 引用不可写
259+
}, std::cref(outside));
260+
env.executor.async(flow).wait();
261+
REQUIRE(seen == 7);
262+
REQUIRE(outside == 7); // 未被改动
263+
}

0 commit comments

Comments
 (0)