| Taskflow: A General-purpose Task-parallel Programming System

Loading...
Searching...
No Matches
flow_builder.hpp
1#pragma once
2
3#include "task.hpp"
4#include "../algorithm/partitioner.hpp"
5
11 namespace tf {
12
13// ------------------------------------------------------------------------------------------------
14// Concept
15// ------------------------------------------------------------------------------------------------
16
31 template <typename C, typename B>
32 concept UnaryOperationLike =
33 requires(C c, B b) {
34 c(*b);
35};
36
52 template <typename C, typename B1, typename B2>
53 concept BinaryOperationLike =
54 requires(C c, B1 b1, B2 b2) {
55 c(*b1, *b2);
56};
57
73 template <typename C, typename B, typename O>
74 concept UnaryTransformLike =
75 requires(C c, B b, O o) {
76 *o = c(*b);
77};
78
95 template <typename C, typename B1, typename B2, typename O>
96 concept BinaryTransformLike =
97 requires(C c, B1 b1, B2 b2, O o) {
98 *o = c(*b1, *b2);
99};
100
101// ------------------------------------------------------------------------------------------------
102// FlowBuilder
103// ------------------------------------------------------------------------------------------------
104
114 class FlowBuilder {
115
116 friend class Executor;
117
118 public:
119
123 FlowBuilder(Graph& graph);
124
143 template <StaticTaskLike C>
144 Task emplace(C&& callable);
145
164 template <RuntimeTaskLike C>
165 Task emplace(C&& callable);
166
189 template <SubflowTaskLike C>
190 Task emplace(C&& callable);
191
222 template <ConditionTaskLike C>
223 Task emplace(C&& callable);
224
256 template <MultiConditionTaskLike C>
257 Task emplace(C&& callable);
258
283 template <typename... C> requires (sizeof...(C) > 1)
284 auto emplace(C&&... callables);
285
306 void erase(Task task);
307
371 template <GraphLike T>
372 Task composed_of(T& object);
373
395 Task adopt(Graph&& graph);
396
425 template <GraphLike T>
426 Task emplace(T& object);
427
454 Task emplace(Graph&& graph);
455
480 Task placeholder();
481
499 void linearize(std::vector<Task>& tasks);
500
516 void linearize(std::initializer_list<Task> tasks);
517
518
519 // ------------------------------------------------------------------------
520 // parallel iterations
521 // ------------------------------------------------------------------------
522
555 template <InputIteratorLike B, InputIteratorLike E, typename C, PartitionerLike P = DefaultPartitioner>
556 requires UnaryOperationLike<C, std::decay_t<std::unwrap_ref_decay_t<B>>>
557 Task for_each(B first, E last, C callable, P part = P());
558
598 template <typename B, typename E, typename S, typename C, PartitionerLike P = DefaultPartitioner>
599 Task for_each_index(B first, E last, S step, C callable, P part = P());
600
717 template <IndexRangesLike R, typename C, PartitionerLike P = DefaultPartitioner>
718 Task for_each_by_index(R range, C callable, P part = P());
719
720 // ------------------------------------------------------------------------
721 // transform
722 // ------------------------------------------------------------------------
723
758 template <InputIteratorLike B, InputIteratorLike E, typename O, typename C,
759 PartitionerLike P = DefaultPartitioner>
760 requires UnaryTransformLike<
761 C,
762 std::decay_t<std::unwrap_ref_decay_t<B>>,
763 std::decay_t<std::unwrap_ref_decay_t<O>>
764 >
765 Task transform(B first1, E last1, O d_first, C c, P part = P());
766
803 template <InputIteratorLike B1, InputIteratorLike E1, InputIteratorLike B2, typename O, typename C,
804 PartitionerLike P = DefaultPartitioner>
805 requires BinaryTransformLike<
806 C,
807 std::decay_t<std::unwrap_ref_decay_t<B1>>,
808 std::decay_t<std::unwrap_ref_decay_t<B2>>,
809 std::decay_t<std::unwrap_ref_decay_t<O>>
810 >
811 Task transform(B1 first1, E1 last1, B2 first2, O d_first, C c, P part = P());
812
813 // ------------------------------------------------------------------------
814 // reduction
815 // ------------------------------------------------------------------------
816
850 template <InputIteratorLike B, InputIteratorLike E, typename T, typename O, PartitionerLike P = DefaultPartitioner>
851 Task reduce(B first, E last, T& init, O bop, P part = P());
852
907 template <IndexRanges1DLike R, typename T, typename L, typename G, PartitionerLike P = DefaultPartitioner>
908 Task reduce_by_index(R range, T& init, L lop, G gop, P part = P());
909
910 // ------------------------------------------------------------------------
911 // transform and reduction
912 // ------------------------------------------------------------------------
913
949 template <InputIteratorLike B, InputIteratorLike E, typename T, typename BOP, typename UOP,
950 PartitionerLike P = DefaultPartitioner>
951 Task transform_reduce(B first, E last, T& init, BOP bop, UOP uop, P part = P());
952
990 template <InputIteratorLike B1, InputIteratorLike E1, InputIteratorLike B2, typename T,
991 typename BOP_R, typename BOP_T, PartitionerLike P = DefaultPartitioner>
992 requires BinaryOperationLike<
993 BOP_T,
994 std::decay_t<std::unwrap_ref_decay_t<B1>>,
995 std::decay_t<std::unwrap_ref_decay_t<B2>>
996 >
997 Task transform_reduce(
998 B1 first1, E1 last1, B2 first2, T& init, BOP_R bop_r, BOP_T bop_t, P part = P()
999 );
1000
1001 // ------------------------------------------------------------------------
1002 // scan
1003 // ------------------------------------------------------------------------
1004
1043 template <InputIteratorLike B, InputIteratorLike E, typename D, typename BOP>
1044 Task inclusive_scan(B first, E last, D d_first, BOP bop);
1045
1087 template <InputIteratorLike B, InputIteratorLike E, typename D, typename BOP, typename T>
1088 Task inclusive_scan(B first, E last, D d_first, BOP bop, T init);
1089
1130 template <InputIteratorLike B, InputIteratorLike E, typename D, typename T, typename BOP>
1131 Task exclusive_scan(B first, E last, D d_first, T init, BOP bop);
1132
1133 // ------------------------------------------------------------------------
1134 // transform scan
1135 // ------------------------------------------------------------------------
1136
1178 template <InputIteratorLike B, InputIteratorLike E, typename D, typename BOP, typename UOP>
1179 Task transform_inclusive_scan(B first, E last, D d_first, BOP bop, UOP uop);
1180
1225 template <InputIteratorLike B, InputIteratorLike E, typename D, typename BOP, typename UOP, typename T>
1226 Task transform_inclusive_scan(B first, E last, D d_first, BOP bop, UOP uop, T init);
1227
1271 template <InputIteratorLike B, InputIteratorLike E, typename D, typename T, typename BOP, typename UOP>
1272 Task transform_exclusive_scan(B first, E last, D d_first, T init, BOP bop, UOP uop);
1273
1274 // ------------------------------------------------------------------------
1275 // find
1276 // ------------------------------------------------------------------------
1277
1323 template <InputIteratorLike B, InputIteratorLike E, typename T, typename UOP, PartitionerLike P = DefaultPartitioner>
1324 Task find_if(B first, E last, T &result, UOP predicate, P part = P());
1325
1371 template <InputIteratorLike B, InputIteratorLike E, typename T, typename UOP, PartitionerLike P = DefaultPartitioner>
1372 Task find_if_not(B first, E last, T &result, UOP predicate, P part = P());
1373
1423 template <InputIteratorLike B, InputIteratorLike E, typename T, typename C, PartitionerLike P>
1424 Task min_element(B first, E last, T& result, C comp, P part);
1425
1475 template <InputIteratorLike B, InputIteratorLike E, typename T, typename C, PartitionerLike P>
1476 Task max_element(B first, E last, T& result, C comp, P part);
1477
1478 // ------------------------------------------------------------------------
1479 // sort
1480 // ------------------------------------------------------------------------
1481
1501 template <InputIteratorLike B, InputIteratorLike E, typename C>
1502 Task sort(B first, E last, C cmp);
1503
1523 template <InputIteratorLike B, InputIteratorLike E>
1524 Task sort(B first, E last);
1525
1559 template <InputIteratorLike B1, InputIteratorLike E1, InputIteratorLike B2, InputIteratorLike E2, typename O>
1560 Task merge(B1 first1, E1 last1, B2 first2, E2 last2, O d_first);
1561
1597 template <InputIteratorLike B1, InputIteratorLike E1,
1598 InputIteratorLike B2, InputIteratorLike E2,
1599 typename O, typename C>
1600 Task merge(B1 first1, E1 last1, B2 first2, E2 last2, O d_first, C cmp);
1601
1627 template<InputIteratorLike B, InputIteratorLike E, typename V, PartitionerLike P = DefaultPartitioner>
1628 Task fill(B first, E last, V value, P part = P());
1629
1655 template<InputIteratorLike B, std::integral C, typename V, PartitionerLike P = DefaultPartitioner>
1656 Task fill_n(B first, C count, V value, P part = P());
1657
1685 template <InputIteratorLike B, InputIteratorLike E, typename G, PartitionerLike P= DefaultPartitioner>
1686 Task generate(B first, E last, G gen, P part = P());
1687
1715 template <InputIteratorLike B, std::integral C, typename G, PartitionerLike P = DefaultPartitioner>
1716 Task generate_n(B first, C count, G gen, P part = P());
1717
1718 protected:
1719
1723 Graph& _graph;
1724
1725 private:
1726
1727 template <typename L>
1728 void _linearize(L&);
1729};
1730
1731// Constructor
1732 inline FlowBuilder::FlowBuilder(Graph& graph) :
1733 _graph {graph} {
1734}
1735
1736// Function: emplace
1737 template <StaticTaskLike C>
1738 Task FlowBuilder::emplace(C&& c) {
1739 return Task(_graph._emplace_back(NSTATE::NONE, ESTATE::NONE, DefaultTaskParams{}, nullptr, nullptr, 0,
1740 std::in_place_type_t<Node::Static>{}, std::forward<C>(c)
1741 ));
1742}
1743
1744// Function: emplace
1745 template <RuntimeTaskLike C>
1746 Task FlowBuilder::emplace(C&& c) {
1747 if constexpr (std::is_invocable_v<C, tf::Runtime&>) {
1748 return Task(_graph._emplace_back(NSTATE::NONE, ESTATE::NONE, DefaultTaskParams{}, nullptr, nullptr, 0,
1749 std::in_place_type_t<Node::Runtime>{}, std::forward<C>(c)
1750 ));
1751 }
1752 else if constexpr (std::is_invocable_v<C, tf::NonpreemptiveRuntime&>) {
1753 return Task(_graph._emplace_back(NSTATE::NONE, ESTATE::NONE, DefaultTaskParams{}, nullptr, nullptr, 0,
1754 std::in_place_type_t<Node::NonpreemptiveRuntime>{}, std::forward<C>(c)
1755 ));
1756 }
1757 else {
1758 static_assert(dependent_false_v<C>, "invalid runtime task callable");
1759 }
1760}
1761
1762// Function: emplace
1763 template <SubflowTaskLike C>
1764Task FlowBuilder::emplace(C&& c) {
1765 return Task(_graph._emplace_back(NSTATE::NONE, ESTATE::NONE, DefaultTaskParams{}, nullptr, nullptr, 0,
1766 std::in_place_type_t<Node::Subflow>{}, std::forward<C>(c)
1767 ));
1768}
1769
1770// Function: emplace
1771 template <ConditionTaskLike C>
1772Task FlowBuilder::emplace(C&& c) {
1773 return Task(_graph._emplace_back(NSTATE::NONE, ESTATE::NONE, DefaultTaskParams{}, nullptr, nullptr, 0,
1774 std::in_place_type_t<Node::Condition>{}, std::forward<C>(c)
1775 ));
1776}
1777
1778// Function: emplace
1779 template <MultiConditionTaskLike C>
1780Task FlowBuilder::emplace(C&& c) {
1781 return Task(_graph._emplace_back(NSTATE::NONE, ESTATE::NONE, DefaultTaskParams{}, nullptr, nullptr, 0,
1782 std::in_place_type_t<Node::MultiCondition>{}, std::forward<C>(c)
1783 ));
1784}
1785
1786// Function: composed_of
1787 template <GraphLike T>
1788 Task FlowBuilder::composed_of(T& target) {
1789 return Task(_graph._emplace_back(NSTATE::NONE, ESTATE::NONE, DefaultTaskParams{}, nullptr, nullptr, 0,
1790 std::in_place_type_t<Node::Module>{}, retrieve_graph(target)
1791 ));
1792}
1793
1794// Function: adopt
1795 inline Task FlowBuilder::adopt(Graph&& graph) {
1796 return Task(_graph._emplace_back(NSTATE::NONE, ESTATE::NONE, DefaultTaskParams{}, nullptr, nullptr, 0,
1797 std::in_place_type_t<Node::AdoptedModule>{}, std::move(graph)
1798 ));
1799}
1800
1801// Function: emplace (convenience overload of composed_of)
1802 template <GraphLike T>
1803 Task FlowBuilder::emplace(T& object) {
1804 return composed_of(object);
1805}
1806
1807// Function: emplace (convenience overload of adopt)
1808 inline Task FlowBuilder::emplace(Graph&& graph) {
1809 return adopt(std::move(graph));
1810}
1811
1812// Function: placeholder
1813 inline Task FlowBuilder::placeholder() {
1814 auto node = _graph._emplace_back(NSTATE::NONE, ESTATE::NONE, DefaultTaskParams{}, nullptr, nullptr, 0,
1815 std::in_place_type_t<Node::Placeholder>{}
1816 );
1817 return Task(node);
1818}
1819
1820// Function: emplace
1821 template <typename... C> requires (sizeof...(C) > 1)
1822 auto FlowBuilder::emplace(C&&... cs) {
1823 return std::make_tuple(emplace(std::forward<C>(cs))...);
1824}
1825
1826// Function: erase
1827 inline void FlowBuilder::erase(Task task) {
1828
1829 if (!task._node) {
1830 return;
1831 }
1832
1833 // remove task from its successors' predecessor list
1834 for(size_t i=0; i<task._node->_num_successors; ++i) {
1835 task._node->_edges[i]->_remove_predecessors(task._node);
1836 }
1837
1838 // remove task from its precedessors' successor list
1839 for(size_t i=task._node->_num_successors; i<task._node->_edges.size(); ++i) {
1840 task._node->_edges[i]->_remove_successors(task._node);
1841 }
1842
1843 _graph._erase(task._node);
1844}
1845
1846
1847// Procedure: _linearize
1848 template <typename L>
1849 void FlowBuilder::_linearize(L& keys) {
1850
1851 auto itr = keys.begin();
1852 auto end = keys.end();
1853
1854 if(itr == end) {
1855 return;
1856 }
1857
1858 auto nxt = itr;
1859
1860 for(++nxt; nxt != end; ++nxt, ++itr) {
1861 itr->_node->_precede(nxt->_node);
1862 }
1863}
1864
1865// Procedure: linearize
1866 inline void FlowBuilder::linearize(std::vector<Task>& keys) {
1867 _linearize(keys);
1868}
1869
1870// Procedure: linearize
1871 inline void FlowBuilder::linearize(std::initializer_list<Task> keys) {
1872 _linearize(keys);
1873}
1874
1875// ----------------------------------------------------------------------------
1876
1913 class Subflow : public FlowBuilder {
1914
1915 friend class Executor;
1916 friend class FlowBuilder;
1917
1918 public:
1919
1935 void join();
1936
1952 bool joinable() const noexcept;
1953
1957 Executor& executor() noexcept;
1958
1962 Graph& graph() { return _graph; }
1963
1973 void retain(bool flag) noexcept;
1974
1982 bool retain() const;
1983
1984 private:
1985
1986 Subflow(Executor&, Worker&, Node*, Graph&);
1987
1988 Subflow() = delete;
1989 Subflow(const Subflow&) = delete;
1990 Subflow(Subflow&&) = delete;
1991
1992 Executor& _executor;
1993 Worker& _worker;
1994 Node* _node;
1995};
1996
1997// Constructor
1998 inline Subflow::Subflow(Executor& executor, Worker& worker, Node* node, Graph& graph) :
1999 FlowBuilder {graph},
2000 _executor {executor},
2001 _worker {worker},
2002 _node {node} {
2003
2004 // need to reset since there could have iterative control flow
2005 _node->_nstate &= ~(NSTATE::JOINED_SUBFLOW | NSTATE::RETAIN_SUBFLOW);
2006
2007 // clear the graph
2008 graph.clear();
2009}
2010
2011// Function: joinable
2012 inline bool Subflow::joinable() const noexcept {
2013 return !(_node->_nstate & NSTATE::JOINED_SUBFLOW);
2014}
2015
2016// Function: executor
2017 inline Executor& Subflow::executor() noexcept {
2018 return _executor;
2019}
2020
2021// Function: retain
2022 inline void Subflow::retain(bool flag) noexcept {
2023 // default value is not to retain
2024 if(flag == true) {
2025 _node->_nstate |= NSTATE::RETAIN_SUBFLOW;
2026 }
2027 else {
2028 _node->_nstate &= ~NSTATE::RETAIN_SUBFLOW;
2029 }
2030
2031 //_node->_nstate = (_node->_nstate & ~NSTATE::RETAIN_SUBFLOW) |
2032 // (-static_cast<int>(flag) & NSTATE::RETAIN_SUBFLOW);
2033}
2034
2035// Function: retain
2036 inline bool Subflow::retain() const {
2037 return _node->_nstate & NSTATE::RETAIN_SUBFLOW;
2038}
2039
2040} // end of namespace tf. ---------------------------------------------------
tf::DefaultTaskParams
class to create an empty task parameter for compile-time optimization
Definition graph.hpp:191
tf::Executor
class to create an executor
Definition executor.hpp:62
tf::FlowBuilder
class to build a task dependency graph
Definition flow_builder.hpp:114
tf::FlowBuilder::inclusive_scan
Task inclusive_scan(B first, E last, D d_first, BOP bop, T init)
creates an STL-styled parallel inclusive-scan task with an initial value
tf::FlowBuilder::inclusive_scan
Task inclusive_scan(B first, E last, D d_first, BOP bop)
creates an STL-styled parallel inclusive-scan task
tf::FlowBuilder::transform
Task transform(B1 first1, E1 last1, B2 first2, O d_first, C c, P part=P())
constructs a parallel-transform task
tf::FlowBuilder::merge
Task merge(B1 first1, E1 last1, B2 first2, E2 last2, O d_first, C cmp)
merges two sorted ranges into a single sorted output using a custom comparator
tf::FlowBuilder::for_each_by_index
Task for_each_by_index(R range, C callable, P part=P())
constructs a parallel-for task over a one- or multi-dimensional index range
tf::FlowBuilder::sort
Task sort(B first, E last, C cmp)
constructs a dynamic task to perform STL-styled parallel sort
tf::FlowBuilder::adopt
Task adopt(Graph &&graph)
creates a module task from a graph by taking over its ownership
Definition flow_builder.hpp:1795
tf::FlowBuilder::generate_n
Task generate_n(B first, C count, G gen, P part=P())
generates N values into a range in parallel using a callable
tf::FlowBuilder::for_each_index
Task for_each_index(B first, E last, S step, C callable, P part=P())
constructs an index-based parallel-for task
tf::FlowBuilder::reduce_by_index
Task reduce_by_index(R range, T &init, L lop, G gop, P part=P())
constructs an index range-based parallel-reduction task
tf::FlowBuilder::transform_reduce
Task transform_reduce(B1 first1, E1 last1, B2 first2, T &init, BOP_R bop_r, BOP_T bop_t, P part=P())
constructs an STL-styled parallel transform-reduce task
tf::FlowBuilder::find_if
Task find_if(B first, E last, T &result, UOP predicate, P part=P())
constructs a task to perform STL-styled find-if algorithm
tf::FlowBuilder::transform_inclusive_scan
Task transform_inclusive_scan(B first, E last, D d_first, BOP bop, UOP uop, T init)
creates an STL-styled parallel transform-inclusive scan task
tf::FlowBuilder::emplace
Task emplace(C &&callable)
creates a static task
Definition flow_builder.hpp:1738
tf::FlowBuilder::exclusive_scan
Task exclusive_scan(B first, E last, D d_first, T init, BOP bop)
creates an STL-styled parallel exclusive-scan task
tf::FlowBuilder::transform_reduce
Task transform_reduce(B first, E last, T &init, BOP bop, UOP uop, P part=P())
constructs an STL-styled parallel transform-reduce task
tf::FlowBuilder::erase
void erase(Task task)
removes a task from a taskflow
Definition flow_builder.hpp:1827
tf::FlowBuilder::for_each
Task for_each(B first, E last, C callable, P part=P())
constructs an STL-styled parallel-for task
tf::FlowBuilder::fill
Task fill(B first, E last, V value, P part=P())
fills a range with a given value in parallel
tf::FlowBuilder::FlowBuilder
FlowBuilder(Graph &graph)
constructs a flow builder with a graph
Definition flow_builder.hpp:1732
tf::FlowBuilder::fill_n
Task fill_n(B first, C count, V value, P part=P())
fills N elements with a given value in parallel
tf::FlowBuilder::max_element
Task max_element(B first, E last, T &result, C comp, P part)
constructs a task to perform STL-styled max-element algorithm
tf::FlowBuilder::min_element
Task min_element(B first, E last, T &result, C comp, P part)
constructs a task to perform STL-styled min-element algorithm
tf::FlowBuilder::generate
Task generate(B first, E last, G gen, P part=P())
generates values into a range in parallel using a callable
tf::FlowBuilder::sort
Task sort(B first, E last)
constructs a dynamic task to perform STL-styled parallel sort using the std::less<T> comparator,...
tf::FlowBuilder::transform_inclusive_scan
Task transform_inclusive_scan(B first, E last, D d_first, BOP bop, UOP uop)
creates an STL-styled parallel transform-inclusive scan task
tf::FlowBuilder::transform_exclusive_scan
Task transform_exclusive_scan(B first, E last, D d_first, T init, BOP bop, UOP uop)
creates an STL-styled parallel transform-exclusive scan task
tf::FlowBuilder::linearize
void linearize(std::vector< Task > &tasks)
adds adjacent dependency links to a linear list of tasks
Definition flow_builder.hpp:1866
tf::FlowBuilder::find_if_not
Task find_if_not(B first, E last, T &result, UOP predicate, P part=P())
constructs a task to perform STL-styled find-if-not algorithm
tf::FlowBuilder::transform
Task transform(B first1, E last1, O d_first, C c, P part=P())
constructs a parallel-transform task
tf::FlowBuilder::composed_of
Task composed_of(T &object)
creates a module task for the target object
Definition flow_builder.hpp:1788
tf::FlowBuilder::placeholder
Task placeholder()
creates a placeholder task
Definition flow_builder.hpp:1813
tf::FlowBuilder::merge
Task merge(B1 first1, E1 last1, B2 first2, E2 last2, O d_first)
merges two sorted ranges into a single sorted output using the std::less comparator
tf::FlowBuilder::reduce
Task reduce(B first, E last, T &init, O bop, P part=P())
constructs an STL-styled parallel-reduction task
tf::Graph
class to create a graph object
Definition graph.hpp:47
tf::Graph::clear
void clear()
clears the graph
Definition graph.hpp:929
tf::GuidedPartitioner
class to create a guided partitioner for scheduling parallel algorithms
Definition partitioner.hpp:417
tf::Subflow
class to construct a subflow graph from the execution of a dynamic task
Definition flow_builder.hpp:1913
tf::Subflow::executor
Executor & executor() noexcept
acquires the associated executor
Definition flow_builder.hpp:2017
tf::Subflow::graph
Graph & graph()
acquires the associated graph
Definition flow_builder.hpp:1962
tf::Subflow::join
void join()
enables the subflow to join its parent task
tf::Subflow::joinable
bool joinable() const noexcept
queries if the subflow is joinable
Definition flow_builder.hpp:2012
tf::Subflow::retain
bool retain() const
queries if the subflow will be retained after it is joined
Definition flow_builder.hpp:2036
tf::Task
class to create a task handle over a taskflow node
Definition task.hpp:569
tf::Worker
class to create a worker in an executor
Definition worker.hpp:55
tf::BinaryOperationLike
concept to check if a binary operation is valid
Definition flow_builder.hpp:53
tf::BinaryTransformLike
concept to check if a binary transformation operation is valid
Definition flow_builder.hpp:96
tf::InputIteratorLike
concept to check if a type is a stateful input iterator
Definition iterator.hpp:1065
tf::PartitionerLike
determines if a type is a partitioner
Definition partitioner.hpp:906
tf::UnaryOperationLike
concept to check if a unary operation is valid
Definition flow_builder.hpp:32
tf::UnaryTransformLike
concept to check if a unary transformation operation is valid
Definition flow_builder.hpp:74
tf
taskflow namespace
Definition small_vector.hpp:20
tf::retrieve_graph
Graph & retrieve_graph(T &target)
retrieves a reference to the underlying tf::Graph from an object
Definition graph.hpp:1067