@@ -36,11 +36,52 @@ class test_vector
3636 data.push_back (value);
3737 }
3838
39+ // Test delegating a write-write
40+ void push_back_2 (const T& v1, const T& v2) {
41+ scl::scoped_check<scl::check_type::write> _ (scl::data_race_registry<>::get_state (this ));
42+ push_back (v1);
43+ push_back (v2);
44+ }
45+
46+ // Test delegating a write-read
47+ T& push_back_and_return (const T& value) {
48+ scl::scoped_check<scl::check_type::write> _ (scl::data_race_registry<>::get_state (this ));
49+ data.push_back (value);
50+ return data.back ();
51+ }
52+
3953 void pop_back () {
4054 scl::scoped_check<scl::check_type::write> _ (scl::data_race_registry<>::get_state (this ));
4155 data.pop_back ();
4256 }
4357
58+ // Test delegating a write-read
59+ const T& push_back_return_old_back (const T& value) {
60+ scl::scoped_check<scl::check_type::write> _ (scl::data_race_registry<>::get_state (this ));
61+
62+ auto & old_back = back ();
63+ push_back (value);
64+ return old_back;
65+ }
66+
67+ T& back () {
68+ scl::scoped_check<scl::check_type::read> _ (scl::data_race_registry<>::get_state (this ));
69+ return data.back ();
70+ }
71+
72+ const T& back () const {
73+ scl::scoped_check<scl::check_type::read> _ (scl::data_race_registry<>::get_state (this ));
74+ return data.back ();
75+ }
76+
77+ // Test delegating a read-write
78+ // This wouldn't normally occur as you'd have a const function calling a non-const function
79+ const T& back_with_default_write () {
80+ scl::scoped_check<scl::check_type::read> _ (scl::data_race_registry<>::get_state (this ));
81+ push_back (T ());
82+ return data.back ();
83+ }
84+
4485 T& operator [](size_t index) {
4586 scl::scoped_check<scl::check_type::read> _ (scl::data_race_registry<>::get_state (this ));
4687 return data[index];
@@ -61,6 +102,13 @@ class test_vector
61102 return data.capacity ();
62103 }
63104
105+ // Test delegating a read-read
106+ std::pair<size_t , size_t > size_and_capacity () const
107+ {
108+ scl::scoped_check<scl::check_type::read> _ (scl::data_race_registry<>::get_state (this ));
109+ return { size (), capacity () };
110+ }
111+
64112private:
65113 std::vector<T> data;
66114};
@@ -74,7 +122,7 @@ inline void test_no_data_race()
74122 vec.push_back (c);
75123
76124 // Then only read from the vector - no data race
77- std::vector<std::thread> threads;
125+ std::vector<std::thread> threads;
78126
79127 for (auto _ : std::ranges::iota_view (0 , 3 ))
80128 threads.emplace_back ([&]
@@ -123,3 +171,57 @@ inline void test_data_race()
123171 for (auto & t : threads)
124172 t.join ();
125173}
174+
175+ inline void test_no_data_race_read_read ()
176+ {
177+ test_vector<size_t > vec;
178+
179+ // Fill the vector first
180+ for (auto c : std::ranges::iota_view (0uz, 1' 000uz))
181+ vec.push_back (c);
182+
183+ // Then only read from the vector - no data race
184+ std::vector<std::thread> threads;
185+
186+ for (auto _ : std::ranges::iota_view (0 , 3 ))
187+ threads.emplace_back ([&]
188+ {
189+ for (auto _ : std::ranges::iota_view (0uz, 1'000' 000uz))
190+ {
191+ volatile auto c = 0uz;
192+
193+ if (! vec.empty ())
194+ c = vec[vec.size () - 1 ];
195+
196+ auto sz_cp = vec.size_and_capacity ();
197+ c = sz_cp.first ;
198+ c = sz_cp.second ;
199+ }
200+ });
201+
202+ for (auto & t : threads)
203+ t.join ();
204+ }
205+
206+ inline void test_no_data_race_read_write ()
207+ {
208+ test_vector<size_t > vec;
209+ volatile auto c = 0uz;
210+ vec.push_back (auto (c));
211+ c = vec.back_with_default_write ();
212+ }
213+
214+ inline void test_no_data_race_write_read ()
215+ {
216+ test_vector<size_t > vec;
217+ volatile auto c = 0uz;
218+ vec.push_back (auto (c));
219+ c = vec.push_back_return_old_back (auto (c));
220+ }
221+
222+ inline void test_no_data_race_write_write ()
223+ {
224+ test_vector<size_t > vec;
225+ volatile auto c = 0uz;
226+ vec.push_back_2 (auto (c), auto (c));
227+ }
0 commit comments