VLink  2.1.0
A high-performance communication middleware
condition_variable.h
Go to the documentation of this file.
1 /*
2  * Copyright (C) 2026 by Thun Lu. All rights reserved.
3  * Author: Thun Lu <thun.lu@zohomail.cn>
4  * Repo: https://github.com/thun-res/vlink
5  * _ __ __ _ __
6  * | | / / / / (_) ____ / /__
7  * | | / / / / / / / __ \ / //_/
8  * | |/ / / /___ / / / / / / / ,<
9  * |___/ /_____/ /_/ /_/ /_/ /_/|_|
10  *
11  * Licensed under the Apache License, Version 2.0 (the "License");
12  * you may not use this file except in compliance with the License.
13  * You may obtain a copy of the License at
14  *
15  * http://www.apache.org/licenses/LICENSE-2.0
16  *
17  * Unless required by applicable law or agreed to in writing, software
18  * distributed under the License is distributed on an "AS IS" BASIS,
19  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20  * See the License for the specific language governing permissions and
21  * limitations under the License.
22  */
23 
24 /**
25  * @file condition_variable.h
26  * @brief Monotonic-clock condition variable replacement immune to system clock jumps.
27  *
28  * @details
29  * Older libstdc++ implementations route every @c std::condition_variable timed wait through
30  * @c CLOCK_REALTIME, so an NTP step or manual date change can spuriously wake or starve waiters
31  * (GCC PR 41861 / DR 887). On POSIX systems this header substitutes a hand-rolled
32  * @c vlink::ConditionVariable backed by a @c pthread_cond_t configured with
33  * @c pthread_condattr_setclock @c (..., @c CLOCK_MONOTONIC). On Windows the bug does not apply
34  * and the names alias to the standard library types verbatim.
35  *
36  * @par API surface vs @c std::condition_variable
37  *
38  * | Aspect | @c std::condition_variable | @c vlink::ConditionVariable |
39  * | ----------------- | --------------------------------------- | ------------------------------------------ |
40  * | Backing clock | @c CLOCK_REALTIME (libstdc++) | @c CLOCK_MONOTONIC via @c pthread_condattr |
41  * | Timed waits | Sensitive to wall clock jumps | Immune to wall clock jumps |
42  * | Copy / move | Deleted | Deleted |
43  * | Public methods | wait / wait_for / wait_until / notify_* | Same signatures, same return types |
44  * | Native handle | @c pthread_cond_t* | @c pthread_cond_t* |
45  *
46  * @par Wait / notify sequence
47  *
48  * @verbatim
49  * producer thread consumer thread
50  * --------------- ---------------
51  * lock(mtx) lock(mtx)
52  * state = ready cv.wait(lock, predicate)
53  * unlock(mtx) releases mtx, blocks
54  * cv.notify_one() -----------> wakes; reacquires mtx
55  * re-checks predicate
56  * returns to caller
57  * @endverbatim
58  *
59  * Both @c ConditionVariable and @c ConditionVariableAny accept any @c std::chrono clock type;
60  * non-steady clock arguments are projected onto @c steady_clock at entry and re-checked at exit
61  * for timeout fidelity. Convenience type aliases @c vlink::condition_variable and
62  * @c vlink::condition_variable_any are also exposed.
63  *
64  * @par Example
65  * @code
66  * std::mutex mtx;
67  * vlink::ConditionVariable cv;
68  * bool ready = false;
69  *
70  * // Consumer:
71  * {
72  * std::unique_lock lock(mtx);
73  * cv.wait_for(lock, std::chrono::milliseconds(200), [&] { return ready; });
74  * }
75  *
76  * // Producer:
77  * {
78  * std::lock_guard lock(mtx);
79  * ready = true;
80  * }
81  * cv.notify_one();
82  * @endcode
83  */
84 
85 #pragma once
86 
87 #include <condition_variable>
88 
89 #if !defined(VLINK_ENABLE_BASE_CONDITION) && defined(__unix__) && !defined(__CYGWIN__)
90 #define VLINK_ENABLE_BASE_CONDITION
91 #endif
92 
93 #ifdef VLINK_ENABLE_BASE_CONDITION
94 #include <pthread.h>
95 
96 #include <chrono>
97 #include <memory>
98 #include <mutex>
99 #include <utility>
100 
101 #include "./macros.h"
102 
103 namespace vlink {
104 
105 /**
106  * @class ConditionVariable
107  * @brief pthread-backed condition variable using @c CLOCK_MONOTONIC for all timed waits.
108  *
109  * @details
110  * Provides the same public interface as @c std::condition_variable. Internally owns a single
111  * @c pthread_cond_t configured at construction with @c pthread_condattr_setclock so the kernel
112  * uses the monotonic clock when timing out waiters; wall-clock arguments are projected onto the
113  * monotonic clock to preserve fidelity.
114  */
115 class VLINK_EXPORT ConditionVariable final {
116  public:
117  /**
118  * @brief Native handle type returned by @c native_handle.
119  */
120  using native_handle_type = pthread_cond_t*;
121 
122  ConditionVariable(const ConditionVariable&) noexcept = delete;
123 
124  ConditionVariable& operator=(const ConditionVariable&) noexcept = delete;
125 
126  /**
127  * @brief Constructs and initialises the underlying @c pthread_cond_t with @c CLOCK_MONOTONIC.
128  */
129  ConditionVariable() noexcept;
130 
131  /**
132  * @brief Destroys the underlying @c pthread_cond_t.
133  */
134  ~ConditionVariable() noexcept;
135 
136  /**
137  * @brief Wakes a single thread currently blocked on this condition variable.
138  */
139  void notify_one() noexcept;
140 
141  /**
142  * @brief Wakes every thread currently blocked on this condition variable.
143  */
144  void notify_all() noexcept;
145 
146  /**
147  * @brief Atomically releases @p lock and suspends until a notification arrives.
148  *
149  * @param lock Held lock to release across the wait.
150  */
151  void wait(std::unique_lock<std::mutex>& lock) noexcept;
152 
153  /**
154  * @brief Waits in a predicate loop until @p p reports @c true.
155  *
156  * @tparam PredicateT Nullary callable returning @c bool.
157  * @param lock Held lock to release across the wait.
158  * @param p Predicate evaluated on each wakeup.
159  * @throws Any exception thrown by @p p.
160  */
161  template <typename PredicateT>
162  void wait(std::unique_lock<std::mutex>& lock, PredicateT p);
163 
164  /**
165  * @brief Steady-clock wait_until overload; the deadline is honoured directly.
166  *
167  * @tparam DurationT Duration type of the time point.
168  * @param lock Held lock to release across the wait.
169  * @param atime Absolute steady-clock deadline.
170  * @return @c std::cv_status::timeout when the deadline passed, otherwise @c no_timeout.
171  */
172  template <typename DurationT>
173  std::cv_status wait_until(std::unique_lock<std::mutex>& lock,
174  const std::chrono::time_point<std::chrono::steady_clock, DurationT>& atime) noexcept;
175 
176  /**
177  * @brief System-clock wait_until overload; the deadline is projected onto steady-clock.
178  *
179  * @tparam DurationT Duration type of the time point.
180  * @param lock Held lock to release across the wait.
181  * @param atime Absolute system-clock deadline.
182  * @return @c std::cv_status::timeout when the deadline passed, otherwise @c no_timeout.
183  */
184  template <typename DurationT>
185  std::cv_status wait_until(std::unique_lock<std::mutex>& lock,
186  const std::chrono::time_point<std::chrono::system_clock, DurationT>& atime) noexcept;
187 
188  /**
189  * @brief Generic clock wait_until overload; the deadline is projected onto steady-clock.
190  *
191  * @tparam ClockT Clock type of the time point.
192  * @tparam DurationT Duration type of the time point.
193  * @param lock Held lock to release across the wait.
194  * @param atime Absolute deadline in @c ClockT.
195  * @return @c std::cv_status::timeout or @c no_timeout.
196  */
197  template <typename ClockT, typename DurationT>
198  std::cv_status wait_until(std::unique_lock<std::mutex>& lock,
199  const std::chrono::time_point<ClockT, DurationT>& atime) noexcept;
200 
201  /**
202  * @brief Predicate-driven wait_until that exits when @p p reports @c true or the deadline elapses.
203  *
204  * @tparam ClockT Clock type of the deadline.
205  * @tparam DurationT Duration type of the deadline.
206  * @tparam PredicateT Nullary callable returning @c bool.
207  * @param lock Held lock to release across the wait.
208  * @param atime Absolute deadline.
209  * @param p Predicate evaluated on each wakeup.
210  * @return Final value of @p p when the call returns.
211  * @throws Any exception thrown by @p p.
212  */
213  template <typename ClockT, typename DurationT, typename PredicateT>
214  bool wait_until(std::unique_lock<std::mutex>& lock, const std::chrono::time_point<ClockT, DurationT>& atime,
215  PredicateT p);
216 
217  /**
218  * @brief Relative wait_for overload anchored on the steady-clock.
219  *
220  * @tparam RepT Duration representation.
221  * @tparam PeriodT Duration period.
222  * @param lock Held lock to release across the wait.
223  * @param rtime Maximum wait duration.
224  * @return @c std::cv_status::timeout or @c no_timeout.
225  */
226  template <typename RepT, typename PeriodT>
227  std::cv_status wait_for(std::unique_lock<std::mutex>& lock,
228  const std::chrono::duration<RepT, PeriodT>& rtime) noexcept;
229 
230  /**
231  * @brief Predicate-driven wait_for that exits when @p p reports @c true or @p rtime elapses.
232  *
233  * @tparam RepT Duration representation.
234  * @tparam PeriodT Duration period.
235  * @tparam PredicateT Nullary callable returning @c bool.
236  * @param lock Held lock to release across the wait.
237  * @param rtime Maximum wait duration.
238  * @param p Predicate evaluated on each wakeup.
239  * @return Final value of @p p when the call returns.
240  * @throws Any exception thrown by @p p.
241  */
242  template <typename RepT, typename PeriodT, typename PredicateT>
243  bool wait_for(std::unique_lock<std::mutex>& lock, const std::chrono::duration<RepT, PeriodT>& rtime, PredicateT p);
244 
245  /**
246  * @brief Returns the underlying @c pthread_cond_t pointer.
247  *
248  * @return Pointer to the internal native condition variable.
249  */
250  [[nodiscard]] native_handle_type native_handle() noexcept;
251 
252  private:
253  template <typename ToDurT, typename RepT, typename PeriodT>
254  static constexpr ToDurT ceil(const std::chrono::duration<RepT, PeriodT>& d) noexcept;
255 
256  template <typename TpT, typename UpT>
257  static constexpr TpT ceil_impl(const TpT& t, const UpT& u) noexcept;
258 
259  std::cv_status wait_until_steady(std::unique_lock<std::mutex>& lock,
260  const std::chrono::steady_clock::time_point& atime) noexcept;
261 
262  pthread_cond_t cond_{};
263 };
264 
265 /**
266  * @class ConditionVariableAny
267  * @brief Monotonic-clock condition variable accepting any @c BasicLockable.
268  *
269  * @details
270  * Mirrors @c std::condition_variable_any while routing all timed waits through the same
271  * pthread-backed monotonic condition variable used by @c ConditionVariable. An internal
272  * @c std::mutex pairs with the shared cv so arbitrary lockables can be unlocked across the
273  * wait and relocked on return.
274  *
275  * @note Behaviour is unspecified if destruction races with any other member function (matches
276  * @c std::condition_variable_any per @c [thread.condition.condvarany]).
277  */
279  public:
280  ConditionVariableAny(const ConditionVariableAny&) noexcept = delete;
281 
282  ConditionVariableAny& operator=(const ConditionVariableAny&) noexcept = delete;
283 
284  /**
285  * @brief Constructs the shared state and underlying condition variable.
286  */
287  ConditionVariableAny() noexcept;
288 
289  /**
290  * @brief Destructor.
291  */
292  ~ConditionVariableAny() noexcept;
293 
294  /**
295  * @brief Wakes a single thread blocked on this condition variable.
296  */
297  void notify_one() noexcept;
298 
299  /**
300  * @brief Wakes every thread blocked on this condition variable.
301  */
302  void notify_all() noexcept;
303 
304  /**
305  * @brief Atomically releases @p lock and suspends until a notification arrives.
306  *
307  * @tparam LockT Any @c BasicLockable type.
308  * @param lock Held lock to release across the wait.
309  */
310  template <typename LockT>
311  void wait(LockT& lock) noexcept;
312 
313  /**
314  * @brief Predicate wait variant that loops until @p p returns @c true.
315  *
316  * @tparam LockT Any @c BasicLockable type.
317  * @tparam PredicateT Nullary callable returning @c bool.
318  * @param lock Held lock to release across the wait.
319  * @param p Predicate evaluated on each wakeup.
320  * @throws Any exception thrown by @p p.
321  */
322  template <typename LockT, typename PredicateT>
323  void wait(LockT& lock, PredicateT p);
324 
325  /**
326  * @brief Generic clock wait_until variant.
327  *
328  * @tparam LockT Any @c BasicLockable type.
329  * @tparam ClockT Clock type of the deadline.
330  * @tparam DurationT Duration type of the deadline.
331  * @param lock Held lock to release across the wait.
332  * @param atime Absolute deadline.
333  * @return @c std::cv_status::timeout or @c no_timeout.
334  */
335  template <typename LockT, typename ClockT, typename DurationT>
336  std::cv_status wait_until(LockT& lock, const std::chrono::time_point<ClockT, DurationT>& atime) noexcept;
337 
338  /**
339  * @brief Predicate-driven wait_until variant.
340  *
341  * @tparam LockT Any @c BasicLockable type.
342  * @tparam ClockT Clock type of the deadline.
343  * @tparam DurationT Duration type of the deadline.
344  * @tparam PredicateT Nullary callable returning @c bool.
345  * @param lock Held lock to release across the wait.
346  * @param atime Absolute deadline.
347  * @param p Predicate evaluated on each wakeup.
348  * @return Final value of @p p when the call returns.
349  * @throws Any exception thrown by @p p.
350  */
351  template <typename LockT, typename ClockT, typename DurationT, typename PredicateT>
352  bool wait_until(LockT& lock, const std::chrono::time_point<ClockT, DurationT>& atime, PredicateT p);
353 
354  /**
355  * @brief Relative wait_for variant.
356  *
357  * @tparam LockT Any @c BasicLockable type.
358  * @tparam RepT Duration representation.
359  * @tparam PeriodT Duration period.
360  * @param lock Held lock to release across the wait.
361  * @param rtime Maximum wait duration.
362  * @return @c std::cv_status::timeout or @c no_timeout.
363  */
364  template <typename LockT, typename RepT, typename PeriodT>
365  std::cv_status wait_for(LockT& lock, const std::chrono::duration<RepT, PeriodT>& rtime) noexcept;
366 
367  /**
368  * @brief Predicate-driven wait_for variant.
369  *
370  * @tparam LockT Any @c BasicLockable type.
371  * @tparam RepT Duration representation.
372  * @tparam PeriodT Duration period.
373  * @tparam PredicateT Nullary callable returning @c bool.
374  * @param lock Held lock to release across the wait.
375  * @param rtime Maximum wait duration.
376  * @param p Predicate evaluated on each wakeup.
377  * @return Final value of @p p when the call returns.
378  * @throws Any exception thrown by @p p.
379  */
380  template <typename LockT, typename RepT, typename PeriodT, typename PredicateT>
381  bool wait_for(LockT& lock, const std::chrono::duration<RepT, PeriodT>& rtime, PredicateT p);
382 
383  private:
384  template <typename ToDurT, typename RepT, typename PeriodT>
385  static constexpr ToDurT ceil(const std::chrono::duration<RepT, PeriodT>& d) noexcept;
386 
387  template <typename TpT, typename UpT>
388  static constexpr TpT ceil_impl(const TpT& t, const UpT& u) noexcept;
389 
390  template <typename LockT, typename DurationT>
391  std::cv_status wait_until_impl(LockT& lock,
392  const std::chrono::time_point<std::chrono::steady_clock, DurationT>& atime) noexcept;
393 
394  struct SharedState final {
395  std::mutex mtx;
397  };
398 
399  std::shared_ptr<SharedState> shared_state_;
400 };
401 
402 ////////////////////////////////////////////////////////////////
403 /// Details
404 ////////////////////////////////////////////////////////////////
405 
406 template <typename PredicateT>
407 inline void ConditionVariable::wait(std::unique_lock<std::mutex>& lock, PredicateT p) {
408  while (!p()) {
409  wait(lock);
410  }
411 }
412 
413 template <typename DurationT>
414 inline std::cv_status ConditionVariable::wait_until(
415  std::unique_lock<std::mutex>& lock,
416  const std::chrono::time_point<std::chrono::steady_clock, DurationT>& atime) noexcept {
417  return wait_until_steady(lock, std::chrono::time_point_cast<std::chrono::steady_clock::duration>(atime));
418 }
419 
420 template <typename DurationT>
421 inline std::cv_status ConditionVariable::wait_until(
422  std::unique_lock<std::mutex>& lock,
423  const std::chrono::time_point<std::chrono::system_clock, DurationT>& atime) noexcept {
424  return wait_until<std::chrono::system_clock, DurationT>(lock, atime);
425 }
426 
427 template <typename ClockT, typename DurationT>
428 inline std::cv_status ConditionVariable::wait_until(std::unique_lock<std::mutex>& lock,
429  const std::chrono::time_point<ClockT, DurationT>& atime) noexcept {
430  const typename ClockT::time_point c_entry = ClockT::now();
431  const std::chrono::steady_clock::time_point s_entry = std::chrono::steady_clock::now();
432  const auto delta = atime - c_entry;
433  const auto s_atime = s_entry + ceil<std::chrono::steady_clock::duration>(delta);
434 
435  if (wait_until_steady(lock, s_atime) == std::cv_status::no_timeout) {
436  return std::cv_status::no_timeout; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
437  }
438 
439  if (ClockT::now() < atime) {
440  return std::cv_status::no_timeout; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
441  }
442 
443  return std::cv_status::timeout;
444 }
445 
446 template <typename ClockT, typename DurationT, typename PredicateT>
447 inline bool ConditionVariable::wait_until(std::unique_lock<std::mutex>& lock,
448  const std::chrono::time_point<ClockT, DurationT>& atime, PredicateT p) {
449  while (!p()) {
450  if (wait_until(lock, atime) == std::cv_status::timeout) {
451  return p();
452  }
453  }
454 
455  return true;
456 }
457 
458 template <typename RepT, typename PeriodT>
459 inline std::cv_status ConditionVariable::wait_for(std::unique_lock<std::mutex>& lock,
460  const std::chrono::duration<RepT, PeriodT>& rtime) noexcept {
461  return wait_until(lock, std::chrono::steady_clock::now() + ceil<std::chrono::steady_clock::duration>(rtime));
462 }
463 
464 template <typename RepT, typename PeriodT, typename PredicateT>
465 inline bool ConditionVariable::wait_for(std::unique_lock<std::mutex>& lock,
466  const std::chrono::duration<RepT, PeriodT>& rtime, PredicateT p) {
467  return wait_until(lock, std::chrono::steady_clock::now() + ceil<std::chrono::steady_clock::duration>(rtime),
468  std::move(p));
469 }
470 
471 template <typename ToDurT, typename RepT, typename PeriodT>
472 inline constexpr ToDurT ConditionVariable::ceil(const std::chrono::duration<RepT, PeriodT>& d) noexcept {
473  return ceil_impl(std::chrono::duration_cast<ToDurT>(d), d);
474 }
475 
476 template <typename TpT, typename UpT>
477 inline constexpr TpT ConditionVariable::ceil_impl(const TpT& t, const UpT& u) noexcept {
478  return (t < u) ? (t + TpT{1}) : t;
479 }
480 
481 template <typename LockT>
482 inline void ConditionVariableAny::wait(LockT& lock) noexcept {
483  std::shared_ptr<SharedState> state = shared_state_;
484  std::unique_lock internal_lock(state->mtx);
485  lock.unlock();
486 
487  struct UnlockGuard final {
488  LockT& lock_ref;
489 
490  ~UnlockGuard() noexcept {
491  try {
492  lock_ref.lock();
493  } catch (std::exception&) { // LCOV_EXCL_LINE GCOVR_EXCL_LINE
494  }
495  }
496  } guard{lock};
497 
498  state->cv.wait(internal_lock);
499  internal_lock.unlock();
500 }
501 
502 template <typename LockT, typename PredicateT>
503 inline void ConditionVariableAny::wait(LockT& lock, PredicateT p) {
504  while (!p()) {
505  wait(lock);
506  }
507 }
508 
509 template <typename LockT, typename ClockT, typename DurationT>
510 inline std::cv_status ConditionVariableAny::wait_until(
511  LockT& lock, const std::chrono::time_point<ClockT, DurationT>& atime) noexcept {
512  if constexpr (std::is_same_v<ClockT, std::chrono::steady_clock>) {
513  return wait_until_impl(lock, atime);
514  } else {
515  const typename ClockT::time_point c_entry = ClockT::now();
516  const std::chrono::steady_clock::time_point s_entry = std::chrono::steady_clock::now();
517  const auto delta = atime - c_entry;
518  const auto s_atime = s_entry + ceil<std::chrono::steady_clock::duration>(delta);
519 
520  if (wait_until_impl(lock, s_atime) == std::cv_status::no_timeout) {
521  return std::cv_status::no_timeout; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
522  }
523 
524  if (ClockT::now() < atime) {
525  return std::cv_status::no_timeout; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
526  }
527 
528  return std::cv_status::timeout;
529  }
530 }
531 
532 template <typename LockT, typename ClockT, typename DurationT, typename PredicateT>
533 inline bool ConditionVariableAny::wait_until(LockT& lock, const std::chrono::time_point<ClockT, DurationT>& atime,
534  PredicateT p) {
535  while (!p()) {
536  if (wait_until(lock, atime) == std::cv_status::timeout) {
537  return p();
538  }
539  }
540 
541  return true;
542 }
543 
544 template <typename LockT, typename RepT, typename PeriodT>
545 inline std::cv_status ConditionVariableAny::wait_for(LockT& lock,
546  const std::chrono::duration<RepT, PeriodT>& rtime) noexcept {
547  return wait_until(lock, std::chrono::steady_clock::now() + ceil<std::chrono::steady_clock::duration>(rtime));
548 }
549 
550 template <typename LockT, typename RepT, typename PeriodT, typename PredicateT>
551 inline bool ConditionVariableAny::wait_for(LockT& lock, const std::chrono::duration<RepT, PeriodT>& rtime,
552  PredicateT p) {
553  return wait_until(lock, std::chrono::steady_clock::now() + ceil<std::chrono::steady_clock::duration>(rtime),
554  std::move(p));
555 }
556 
557 template <typename ToDurT, typename RepT, typename PeriodT>
558 inline constexpr ToDurT ConditionVariableAny::ceil(const std::chrono::duration<RepT, PeriodT>& d) noexcept {
559  return ceil_impl(std::chrono::duration_cast<ToDurT>(d), d);
560 }
561 
562 template <typename TpT, typename UpT>
563 inline constexpr TpT ConditionVariableAny::ceil_impl(const TpT& t, const UpT& u) noexcept {
564  return (t < u) ? (t + TpT{1}) : t;
565 }
566 
567 template <typename LockT, typename DurationT>
568 inline std::cv_status ConditionVariableAny::wait_until_impl(
569  LockT& lock, const std::chrono::time_point<std::chrono::steady_clock, DurationT>& atime) noexcept {
570  if (std::chrono::steady_clock::now() >= atime) {
571  return std::cv_status::timeout;
572  }
573 
574  std::shared_ptr<SharedState> state = shared_state_;
575  std::unique_lock internal_lock(state->mtx);
576  lock.unlock();
577 
578  struct UnlockGuard final {
579  LockT& lock_ref;
580 
581  ~UnlockGuard() noexcept {
582  try {
583  lock_ref.lock();
584  } catch (std::exception&) { // LCOV_EXCL_LINE GCOVR_EXCL_LINE
585  }
586  }
587  } guard{lock};
588 
589  const auto status = state->cv.wait_until(internal_lock, atime);
590  internal_lock.unlock();
591 
592  return status;
593 }
594 
595 /**
596  * @typedef condition_variable
597  * @brief Snake-case alias for @c ConditionVariable.
598  */
600 
601 /**
602  * @typedef condition_variable_any
603  * @brief Snake-case alias for @c ConditionVariableAny.
604  */
606 
607 } // namespace vlink
608 
609 #else
610 
611 namespace vlink {
612 
617 
618 } // namespace vlink
619 
620 #endif
Cross-platform macros for visibility, branch hints, copy prevention, singletons and string helpers.
#define VLINK_EXPORT
Definition: macros.h:81