susumu.yata
null+****@clear*****
Wed Aug 20 18:54:40 JST 2014
susumu.yata 2014-08-20 18:54:40 +0900 (Wed, 20 Aug 2014) New Revision: 4fce3f5e024ad4ce89804414defc674af8cd9e67 https://github.com/groonga/grnxx/commit/4fce3f5e024ad4ce89804414defc674af8cd9e67 Message: Add a rough implementation of Pipeline. Added files: include/grnxx/pipeline.hpp lib/grnxx/pipeline.cpp Modified files: include/grnxx/Makefile.am include/grnxx/expression.hpp include/grnxx/sorter.hpp lib/grnxx/Makefile.am lib/grnxx/expression.cpp lib/grnxx/sorter.cpp Modified: include/grnxx/Makefile.am (+1 -0) =================================================================== --- include/grnxx/Makefile.am 2014-08-20 16:35:03 +0900 (a83e849) +++ include/grnxx/Makefile.am 2014-08-20 18:54:40 +0900 (e147311) @@ -8,6 +8,7 @@ pkginclude_HEADERS = \ expression.hpp \ index.hpp \ library.hpp \ + pipeline.hpp \ sorter.hpp \ table.hpp \ types.hpp Modified: include/grnxx/expression.hpp (+27 -26) =================================================================== --- include/grnxx/expression.hpp 2014-08-20 16:35:03 +0900 (79c497a) +++ include/grnxx/expression.hpp 2014-08-20 18:54:40 +0900 (88702a8) @@ -11,8 +11,6 @@ class Node; } // namespace expression -using ExpressionNode = expression::Node; - enum OperatorType { // -- Unary operators -- @@ -67,6 +65,8 @@ enum OperatorType { class Expression { public: + using Node = expression::Node; + ~Expression(); // Return the associated table. @@ -180,7 +180,7 @@ class Expression { private: const Table *table_; - unique_ptr<ExpressionNode> root_; + unique_ptr<Node> root_; Int block_size_; // Create an expression. @@ -190,11 +190,11 @@ class Expression { // "error" != nullptr. static unique_ptr<Expression> create(Error *error, const Table *table, - unique_ptr<ExpressionNode> &&root, + unique_ptr<Node> &&root, const ExpressionOptions &options); Expression(const Table *table, - unique_ptr<ExpressionNode> &&root, + unique_ptr<Node> &&root, Int block_size); friend ExpressionBuilder; @@ -202,6 +202,8 @@ class Expression { class ExpressionBuilder { public: + using Node = expression::Node; + // Create an object for building expressons. // // On success, returns a poitner to the builder. @@ -261,15 +263,14 @@ class ExpressionBuilder { private: const Table *table_; - Array<unique_ptr<ExpressionNode>> stack_; + Array<unique_ptr<Node>> stack_; explicit ExpressionBuilder(const Table *table); // Create a node associated with a constant. - unique_ptr<ExpressionNode> create_datum_node(Error *error, - const Datum &datum); + unique_ptr<Node> create_datum_node(Error *error, const Datum &datum); // Create a node associated with a column. - unique_ptr<ExpressionNode> create_column_node(Error *error, String name); + unique_ptr<Node> create_column_node(Error *error, String name); // Push a unary operator. bool push_unary_operator(Error *error, OperatorType operator_type); @@ -277,43 +278,43 @@ class ExpressionBuilder { bool push_binary_operator(Error *error, OperatorType operator_type); // Create a node associated with a unary operator. - unique_ptr<ExpressionNode> create_unary_node( + unique_ptr<Node> create_unary_node( Error *error, OperatorType operator_type, - unique_ptr<ExpressionNode> &&arg); + unique_ptr<Node> &&arg); // Create a node associated with a binary operator. - unique_ptr<ExpressionNode> create_binary_node( + unique_ptr<Node> create_binary_node( Error *error, OperatorType operator_type, - unique_ptr<ExpressionNode> &&arg1, - unique_ptr<ExpressionNode> &&arg2); + unique_ptr<Node> &&arg1, + unique_ptr<Node> &&arg2); // Create a equality test node. template <typename T> - unique_ptr<ExpressionNode> create_equality_test_node( + unique_ptr<Node> create_equality_test_node( Error *error, - unique_ptr<ExpressionNode> &&arg1, - unique_ptr<ExpressionNode> &&arg2); + unique_ptr<Node> &&arg1, + unique_ptr<Node> &&arg2); // Create a comparison node. template <typename T> - unique_ptr<ExpressionNode> create_comparison_node( + unique_ptr<Node> create_comparison_node( Error *error, - unique_ptr<ExpressionNode> &&arg1, - unique_ptr<ExpressionNode> &&arg2); + unique_ptr<Node> &&arg1, + unique_ptr<Node> &&arg2); // Create a bitwise node. template <typename T> - unique_ptr<ExpressionNode> create_bitwise_node( + unique_ptr<Node> create_bitwise_node( Error *error, OperatorType operator_type, - unique_ptr<ExpressionNode> &&arg1, - unique_ptr<ExpressionNode> &&arg2); + unique_ptr<Node> &&arg1, + unique_ptr<Node> &&arg2); // Create an arithmetic node. template <typename T> - unique_ptr<ExpressionNode> create_arithmetic_node( + unique_ptr<Node> create_arithmetic_node( Error *error, OperatorType operator_type, - unique_ptr<ExpressionNode> &&arg1, - unique_ptr<ExpressionNode> &&arg2); + unique_ptr<Node> &&arg1, + unique_ptr<Node> &&arg2); }; } // namespace grnxx Added: include/grnxx/pipeline.hpp (+120 -0) 100644 =================================================================== --- /dev/null +++ include/grnxx/pipeline.hpp 2014-08-20 18:54:40 +0900 (47fe582) @@ -0,0 +1,120 @@ +#ifndef GRNXX_PIPELINE_HPP +#define GRNXX_PIPELINE_HPP + +#include "grnxx/array.hpp" +#include "grnxx/sorter.hpp" +#include "grnxx/types.hpp" + +namespace grnxx { +namespace pipeline { + +class Node; + +} // namespace pipeline + +using PipelineNode = pipeline::Node; + +struct PipelineOptions { +}; + +class Pipeline { + public: + ~Pipeline(); + + const Table *table() const { + return table_; + } + + // Read all the records through the pipeline. + // + // On success, returns true. + // On failure, returns false and stores error information into "*error" if + // "error" != nullptr. + bool flush(Error *error, Array<Record> *records); + + private: + const Table *table_; + unique_ptr<PipelineNode> root_; + + static unique_ptr<Pipeline> create(Error *error, + const Table *table, + unique_ptr<PipelineNode> &&root, + const PipelineOptions &options); + + Pipeline(const Table *table, + unique_ptr<PipelineNode> &&root); + + friend class PipelineBuilder; +}; + +class PipelineBuilder { + public: + // Create an object for building a pipeline. + // + // On success, returns a poitner to the builder. + // On failure, returns nullptr and stores error information into "*error" if + // "error" != nullptr. + static unique_ptr<PipelineBuilder> create(Error *error, const Table *table); + + ~PipelineBuilder(); + + // Return the associated table. + const Table *table() const { + return table_; + } + + // Push a cursor. + // + // On success, returns true. + // On failure, returns false and stores error information into "*error" if + // "error" != nullptr. + bool push_cursor(Error *error, unique_ptr<Cursor> &&cursor); + + // Push a filter. + // + // On success, returns true. + // On failure, returns false and stores error information into "*error" if + // "error" != nullptr. + bool push_filter(Error *error, + unique_ptr<Expression> &&expression, + Int offset, Int limit); + + // Push an adjuster. + // + // On success, returns true. + // On failure, returns false and stores error information into "*error" if + // "error" != nullptr. + bool push_adjuster(Error *error, unique_ptr<Expression> &&expression); + + // Push a sorter. + // + // On success, returns true. + // On failure, returns false and stores error information into "*error" if + // "error" != nullptr. + bool push_sorter(Error *error, unique_ptr<Sorter> &&sorter); + + // TODO: Not supported yet. +// bool push_merger(Error *error, const MergerOptions &options); + + // Clear the internal stack. + void clear(); + + // Complete building a pipeline and clear the internal stack. + // + // Fails if the stack is empty or contains more than one nodes. + // + // On success, returns a pointer to the expression. + // On failure, returns nullptr and stores error information into "*error" if + // "error" != nullptr. + unique_ptr<Pipeline> release(Error *error, const PipelineOptions &options); + + private: + const Table *table_; + Array<unique_ptr<PipelineNode>> stack_; + + PipelineBuilder(); +}; + +} // namespace grnxx + +#endif // GRNXX_PIPELINE_HPP Modified: include/grnxx/sorter.hpp (+3 -3) =================================================================== --- include/grnxx/sorter.hpp 2014-08-20 16:35:03 +0900 (6c4b179) +++ include/grnxx/sorter.hpp 2014-08-20 18:54:40 +0900 (6948745) @@ -11,10 +11,10 @@ class Node; } // namespace sorter -using SorterNode = sorter::Node; - class Sorter { public: + using Node = sorter::Node; + ~Sorter(); // Return the associated table. @@ -69,7 +69,7 @@ class Sorter { private: const Table *table_; - unique_ptr<SorterNode> head_; + unique_ptr<Node> head_; Array<Record> *records_; Int offset_; Int limit_; Modified: lib/grnxx/Makefile.am (+1 -0) =================================================================== --- lib/grnxx/Makefile.am 2014-08-20 16:35:03 +0900 (a9e9b12) +++ lib/grnxx/Makefile.am 2014-08-20 18:54:40 +0900 (4c34688) @@ -17,6 +17,7 @@ libgrnxx_la_SOURCES = \ index.cpp \ library.cpp \ name.cpp \ + pipeline.cpp \ sorter.cpp \ table.cpp \ types.cpp Modified: lib/grnxx/expression.cpp (+23 -23) =================================================================== --- lib/grnxx/expression.cpp 2014-08-20 16:35:03 +0900 (bbcbd5f) +++ lib/grnxx/expression.cpp 2014-08-20 18:54:40 +0900 (e443683) @@ -2333,7 +2333,7 @@ bool Expression::evaluate(Error *error, unique_ptr<Expression> Expression::create(Error *error, const Table *table, - unique_ptr<ExpressionNode> &&root, + unique_ptr<Node> &&root, const ExpressionOptions &options) { if (options.block_size <= 0) { GRNXX_ERROR_SET(error, INVALID_ARGUMENT, @@ -2351,7 +2351,7 @@ unique_ptr<Expression> Expression::create(Error *error, } Expression::Expression(const Table *table, - unique_ptr<ExpressionNode> &&root, + unique_ptr<Node> &&root, Int block_size) : table_(table), root_(std::move(root)), @@ -2391,7 +2391,7 @@ bool ExpressionBuilder::push_column(Error *error, String name) { if (!stack_.reserve(error, stack_.size() + 1)) { return false; } - unique_ptr<ExpressionNode> node = create_column_node(error, name); + unique_ptr<Node> node = create_column_node(error, name); if (!node) { return false; } @@ -2448,7 +2448,7 @@ unique_ptr<Expression> ExpressionBuilder::release( GRNXX_ERROR_SET(error, INVALID_ARGUMENT, "Incomplete expression"); return nullptr; } - unique_ptr<ExpressionNode> root = std::move(stack_[0]); + unique_ptr<Node> root = std::move(stack_[0]); stack_.clear(); return Expression::create(error, table_, std::move(root), options); } @@ -2457,7 +2457,7 @@ ExpressionBuilder::ExpressionBuilder(const Table *table) : table_(table), stack_() {} -unique_ptr<ExpressionNode> ExpressionBuilder::create_datum_node( +unique_ptr<Node> ExpressionBuilder::create_datum_node( Error *error, const Datum &datum) { switch (datum.type()) { @@ -2487,7 +2487,7 @@ unique_ptr<ExpressionNode> ExpressionBuilder::create_datum_node( } } -unique_ptr<ExpressionNode> ExpressionBuilder::create_column_node( +unique_ptr<Node> ExpressionBuilder::create_column_node( Error *error, String name) { if (name == "_id") { @@ -2577,10 +2577,10 @@ bool ExpressionBuilder::push_binary_operator(Error *error, return true; } -unique_ptr<ExpressionNode> ExpressionBuilder::create_unary_node( +unique_ptr<Node> ExpressionBuilder::create_unary_node( Error *error, OperatorType operator_type, - unique_ptr<ExpressionNode> &&arg) { + unique_ptr<Node> &&arg) { switch (operator_type) { case LOGICAL_NOT_OPERATOR: { if (arg->data_type() != BOOL_DATA) { @@ -2647,11 +2647,11 @@ unique_ptr<ExpressionNode> ExpressionBuilder::create_unary_node( } } -unique_ptr<ExpressionNode> ExpressionBuilder::create_binary_node( +unique_ptr<Node> ExpressionBuilder::create_binary_node( Error *error, OperatorType operator_type, - unique_ptr<ExpressionNode> &&arg1, - unique_ptr<ExpressionNode> &&arg2) { + unique_ptr<Node> &&arg1, + unique_ptr<Node> &&arg2) { switch (operator_type) { case LOGICAL_AND_OPERATOR: { if ((arg1->data_type() != BOOL_DATA) || @@ -2747,10 +2747,10 @@ unique_ptr<ExpressionNode> ExpressionBuilder::create_binary_node( } template <typename T> -unique_ptr<ExpressionNode> ExpressionBuilder::create_equality_test_node( +unique_ptr<Node> ExpressionBuilder::create_equality_test_node( Error *error, - unique_ptr<ExpressionNode> &&arg1, - unique_ptr<ExpressionNode> &&arg2) { + unique_ptr<Node> &&arg1, + unique_ptr<Node> &&arg2) { if (arg1->data_type() != arg2->data_type()) { GRNXX_ERROR_SET(error, INVALID_OPERAND, "Data type conflict"); return nullptr; @@ -2789,10 +2789,10 @@ unique_ptr<ExpressionNode> ExpressionBuilder::create_equality_test_node( } template <typename T> -unique_ptr<ExpressionNode> ExpressionBuilder::create_comparison_node( +unique_ptr<Node> ExpressionBuilder::create_comparison_node( Error *error, - unique_ptr<ExpressionNode> &&arg1, - unique_ptr<ExpressionNode> &&arg2) { + unique_ptr<Node> &&arg1, + unique_ptr<Node> &&arg2) { if (arg1->data_type() != arg2->data_type()) { GRNXX_ERROR_SET(error, INVALID_OPERAND, "Data type conflict"); return nullptr; @@ -2822,11 +2822,11 @@ unique_ptr<ExpressionNode> ExpressionBuilder::create_comparison_node( } template <typename T> -unique_ptr<ExpressionNode> ExpressionBuilder::create_bitwise_node( +unique_ptr<Node> ExpressionBuilder::create_bitwise_node( Error *error, OperatorType operator_type, - unique_ptr<ExpressionNode> &&arg1, - unique_ptr<ExpressionNode> &&arg2) { + unique_ptr<Node> &&arg1, + unique_ptr<Node> &&arg2) { if (arg1->data_type() != arg2->data_type()) { GRNXX_ERROR_SET(error, INVALID_OPERAND, "Data type conflict"); return nullptr; @@ -2852,11 +2852,11 @@ unique_ptr<ExpressionNode> ExpressionBuilder::create_bitwise_node( } template <typename T> -unique_ptr<ExpressionNode> ExpressionBuilder::create_arithmetic_node( +unique_ptr<Node> ExpressionBuilder::create_arithmetic_node( Error *error, OperatorType operator_type, - unique_ptr<ExpressionNode> &&arg1, - unique_ptr<ExpressionNode> &&arg2) { + unique_ptr<Node> &&arg1, + unique_ptr<Node> &&arg2) { if (arg1->data_type() != arg2->data_type()) { GRNXX_ERROR_SET(error, INVALID_OPERAND, "Data type conflict"); return nullptr; Added: lib/grnxx/pipeline.cpp (+284 -0) 100644 =================================================================== --- /dev/null +++ lib/grnxx/pipeline.cpp 2014-08-20 18:54:40 +0900 (856725b) @@ -0,0 +1,284 @@ +#include "grnxx/pipeline.hpp" + +#include "grnxx/cursor.hpp" +#include "grnxx/error.hpp" +#include "grnxx/expression.hpp" + +namespace grnxx { +namespace pipeline { + +class Node { + public: + Node() {} + virtual ~Node() {} + + virtual Int read_next(Error *error, Array<Record> *records) = 0; + virtual Int read_all(Error *error, Array<Record> *records); +}; + +Int Node::read_all(Error *error, Array<Record> *records) { + Int total_count = 0; + for ( ; ; ) { + Int count = read_next(error, records); + if (count == -1) { + return -1; + } else if (count == 0) { + break; + } + total_count += count; + } + return total_count; +} + +class CursorNode : public Node { + public: + explicit CursorNode(unique_ptr<Cursor> &&cursor) + : Node(), + cursor_(std::move(cursor)) {} + ~CursorNode() {} + + Int read_next(Error *error, Array<Record> *records); + Int read_all(Error *error, Array<Record> *records); + + private: + unique_ptr<Cursor> cursor_; +}; + +Int CursorNode::read_next(Error *error, Array<Record> *records) { + // TODO: The following block size (1024) should be optimized. + return cursor_->read(error, 1024, records); +} + +Int CursorNode::read_all(Error *error, Array<Record> *records) { + return cursor_->read_all(error, records); +} + +class FilterNode : public Node { + public: + FilterNode(unique_ptr<Node> &&arg, + unique_ptr<Expression> &&expression, + Int offset, + Int limit) + : Node(), + arg_(std::move(arg)), + expression_(std::move(expression)), + offset_(offset), + limit_(limit) {} + ~FilterNode() {} + + Int read_next(Error *error, Array<Record> *records); + + private: + unique_ptr<Node> arg_; + unique_ptr<Expression> expression_; + Int offset_; + Int limit_; +}; + +Int FilterNode::read_next(Error *error, Array<Record> *records) { + // TODO: The following threshold (1024) should be optimized. + Int offset = records->size(); + for ( ; ; ) { + Int count = arg_->read_next(error, records); + if (count == -1) { + return -1; + } else if (count == 0) { + break; + } + // TODO: offset and limit are not supported yet. + if (!expression_->filter(error, records, offset)) { + return -1; + } + if (records->size() - offset) { + break; + } + } + return records->size() - offset; +} + +class AdjusterNode : public Node { + public: + explicit AdjusterNode(unique_ptr<Node> &&arg, + unique_ptr<Expression> &&expression) + : Node(), + arg_(std::move(arg)), + expression_(std::move(expression)) {} + ~AdjusterNode() {} + + Int read_next(Error *error, Array<Record> *records); + + private: + unique_ptr<Node> arg_; + unique_ptr<Expression> expression_; +}; + +Int AdjusterNode::read_next(Error *error, Array<Record> *records) { + Int offset = records->size(); + Int count = arg_->read_next(error, records); + if (count == -1) { + return -1; + } + if (!expression_->adjust(error, records, offset)) { + return -1; + } + return count; +} + +class SorterNode : public Node { + public: + explicit SorterNode(unique_ptr<Node> &&arg, + unique_ptr<Sorter> &&sorter) + : Node(), + arg_(std::move(arg)), + sorter_(std::move(sorter)) {} + ~SorterNode() {} + + Int read_next(Error *error, Array<Record> *records); + + private: + unique_ptr<Node> arg_; + unique_ptr<Sorter> sorter_; +}; + +Int SorterNode::read_next(Error *error, Array<Record> *records) { + Int count = arg_->read_all(error, records); + if (count == -1) { + return -1; + } else if (count == 0) { + return 0; + } + if (!sorter_->sort(error, records)) { + return -1; + } + return records->size(); +} + +} // namespace pipeline + +using namespace pipeline; + +Pipeline::~Pipeline() {} + +bool Pipeline::flush(Error *error, Array<Record> *records) { + return root_->read_all(error, records) >= 0; +} + +unique_ptr<Pipeline> Pipeline::create(Error *error, + const Table *table, + unique_ptr<PipelineNode> &&root, + const PipelineOptions &options) { + unique_ptr<Pipeline> pipeline( + new (nothrow) Pipeline(table, std::move(root))); + if (!pipeline) { + GRNXX_ERROR_SET(error, NO_MEMORY, "Memory allocation failed"); + return nullptr; + } + return pipeline; +} + +Pipeline::Pipeline(const Table *table, + unique_ptr<PipelineNode> &&root) + : table_(table), + root_(std::move(root)) {} + +unique_ptr<PipelineBuilder> PipelineBuilder::create(Error *error, + const Table *table) { + unique_ptr<PipelineBuilder> builder(new (nothrow) PipelineBuilder); + if (!builder) { + GRNXX_ERROR_SET(error, NO_MEMORY, "Memory allocation failed"); + return nullptr; + } + builder->table_ = table; + return builder; +} + +PipelineBuilder::~PipelineBuilder() {} + +bool PipelineBuilder::push_cursor(Error *error, unique_ptr<Cursor> &&cursor) { + // Reserve a space for a new node. + if (!stack_.reserve(error, stack_.size() + 1)) { + return false; + } + unique_ptr<Node> node(new (nothrow) CursorNode(std::move(cursor))); + if (!node) { + GRNXX_ERROR_SET(error, NO_MEMORY, "Memory allocation failed"); + return false; + } + // This push_back() must not fail because a space is already reserved. + stack_.push_back(nullptr, std::move(node)); + return true; +} + +bool PipelineBuilder::push_filter(Error *error, + unique_ptr<Expression> &&expression, + Int offset, Int limit) { + if (stack_.size() < 1) { + GRNXX_ERROR_SET(error, INVALID_OPERAND, "Not enough nodes"); + return false; + } + unique_ptr<Node> arg = std::move(stack_[stack_.size() - 1]); + stack_.resize(nullptr, stack_.size() - 1); + unique_ptr<Node> node( + new (nothrow) FilterNode(std::move(arg), std::move(expression), + offset, limit)); + if (!node) { + GRNXX_ERROR_SET(error, NO_MEMORY, "Memory allocation failed"); + return false; + } + stack_.push_back(error, std::move(node)); + return true; +} + +bool PipelineBuilder::push_adjuster(Error *error, + unique_ptr<Expression> &&expression) { + if (stack_.size() < 1) { + GRNXX_ERROR_SET(error, INVALID_OPERAND, "Not enough nodes"); + return false; + } + unique_ptr<Node> arg = std::move(stack_[stack_.size() - 1]); + stack_.resize(nullptr, stack_.size() - 1); + unique_ptr<Node> node( + new (nothrow) AdjusterNode(std::move(arg), std::move(expression))); + if (!node) { + GRNXX_ERROR_SET(error, NO_MEMORY, "Memory allocation failed"); + return false; + } + stack_.push_back(error, std::move(node)); + return true; +} + +bool PipelineBuilder::push_sorter(Error *error, unique_ptr<Sorter> &&sorter) { + if (stack_.size() < 1) { + GRNXX_ERROR_SET(error, INVALID_OPERAND, "Not enough nodes"); + return false; + } + unique_ptr<Node> arg = std::move(stack_[stack_.size() - 1]); + stack_.resize(nullptr, stack_.size() - 1); + unique_ptr<Node> node( + new (nothrow) SorterNode(std::move(arg), std::move(sorter))); + if (!node) { + GRNXX_ERROR_SET(error, NO_MEMORY, "Memory allocation failed"); + return false; + } + stack_.push_back(error, std::move(node)); + return true; +} + +void PipelineBuilder::clear() { + stack_.clear(); +} + +unique_ptr<Pipeline> PipelineBuilder::release(Error *error, + const PipelineOptions &options) { + if (stack_.size() != 1) { + GRNXX_ERROR_SET(error, INVALID_ARGUMENT, "Incomplete pipeline"); + return nullptr; + } + unique_ptr<PipelineNode> root = std::move(stack_[0]); + stack_.clear(); + return Pipeline::create(error, table_, std::move(root), options); +} + +PipelineBuilder::PipelineBuilder() : table_(nullptr), stack_() {} + +} // namespace grnxx Modified: lib/grnxx/sorter.cpp (+3 -4) =================================================================== --- lib/grnxx/sorter.cpp 2014-08-20 16:35:03 +0900 (7c1e791) +++ lib/grnxx/sorter.cpp 2014-08-20 18:54:40 +0900 (2819b0b) @@ -443,8 +443,8 @@ struct ReverseComparer { using namespace sorter; -unique_ptr<SorterNode> SorterNode::create(Error *error, SortOrder &&order) { - unique_ptr<SorterNode> node; +unique_ptr<Node> Node::create(Error *error, SortOrder &&order) { + unique_ptr<Node> node; switch (order.expression->data_type()) { case BOOL_DATA: { if (order.type == REGULAR_ORDER) { @@ -554,8 +554,7 @@ unique_ptr<Sorter> Sorter::create( } sorter->table_ = table; for (Int i = orders.size() - 1; i >= 0; --i) { - unique_ptr<SorterNode> node( - SorterNode::create(error, std::move(orders[i]))); + unique_ptr<Node> node(Node::create(error, std::move(orders[i]))); if (!node) { return nullptr; } -------------- next part -------------- HTML����������������������������...Descargar