Fix the crash issue in LSTM multithreading

Changed the WERD_RES linked link to use shared pointers instead of raw pointers.
This is needed so that even if one thread deletes a WERD_RES object, other thread's which needs to iterate thru them can still access it safely.
In terms of LSTM processing, only one threads processes one WERD_RES. This change is needed as all the threads can iterate thru due to single linked list data structure.
This commit is contained in:
Karthick J 2024-07-05 19:27:07 +05:30
parent b7d6739701
commit d1eed6a821
8 changed files with 338 additions and 164 deletions

View File

@ -203,8 +203,8 @@ bool Tesseract::RecogWordsSegment(std::vector<WordData>::iterator start,
LSTMRecognizer *lstm_recognizer,
std::atomic<int>& words_done,
int total_words,
std::mutex& monitor_mutex) {
PAGE_RES_IT pr_it(page_res);
std::shared_ptr<std::mutex> recog_words_mutex) {
PAGE_RES_IT pr_it(page_res, recog_words_mutex);
// Process a segment of the words vector
pr_it.restart_page();
@ -214,7 +214,7 @@ bool Tesseract::RecogWordsSegment(std::vector<WordData>::iterator start,
word->prev_word = &(*(it - 1));
}
if (monitor != nullptr) {
std::lock_guard<std::mutex> lock(monitor_mutex);
std::lock_guard<std::mutex> lock(*recog_words_mutex);
monitor->ocr_alive = true;
if (pass_n == 1) {
monitor->progress = 70 * words_done / total_words;
@ -245,9 +245,7 @@ bool Tesseract::RecogWordsSegment(std::vector<WordData>::iterator start,
}
}
// Sync pr_it with the WordData.
while (pr_it.word() != nullptr && pr_it.word() != word->word) {
pr_it.forward();
}
pr_it.forward_to_word(word->word);
ASSERT_HOST(pr_it.word() != nullptr);
bool make_next_word_fuzzy = false;
#ifndef DISABLED_LEGACY_ENGINE
@ -274,19 +272,24 @@ bool Tesseract::RecogWordsSegment(std::vector<WordData>::iterator start,
bool Tesseract::RecogAllWordsPassN(int pass_n, ETEXT_DESC *monitor, PAGE_RES *page_res,
std::vector<WordData> *words) {
int total_words = words->size();
int segment_size = total_words / lstm_num_threads;
int segment_size = std::max(total_words / lstm_num_threads, 1);
std::atomic<int> words_done(0);
std::mutex monitor_mutex;
std::shared_ptr<std::mutex> recog_words_mutex = std::make_shared<std::mutex>();
std::vector<std::future<bool>> futures;
// Launch multiple threads to recognize the words in parallel
auto segment_start = words->begin() + segment_size;
for (int i = 1; i < lstm_num_threads; ++i) {
auto segment_end = (i == lstm_num_threads - 1) ? words->end() : segment_start + segment_size;
futures.push_back(std::async(std::launch::async, &Tesseract::RecogWordsSegment,
this, segment_start, segment_end, pass_n, monitor, page_res,
lstm_recognizers_[i], std::ref(words_done), total_words, std::ref(monitor_mutex)));
segment_start = segment_end;
for (int i = 1; i < lstm_num_threads && segment_start != words->end(); ++i) {
auto segment_end = segment_start + segment_size;
if (i == lstm_num_threads - 1 ||
std::distance(segment_start, words->end()) < segment_size) {
segment_end = words->end();
}
futures.push_back(std::async(
std::launch::async, &Tesseract::RecogWordsSegment, this, segment_start,
segment_end, pass_n, monitor, page_res, lstm_recognizers_[i],
std::ref(words_done), total_words, recog_words_mutex));
segment_start = segment_end;
}
// Process the first segment in this thread
@ -298,7 +301,7 @@ bool Tesseract::RecogAllWordsPassN(int pass_n, ETEXT_DESC *monitor, PAGE_RES *pa
lstm_recognizers_[0],
std::ref(words_done),
total_words,
std::ref(monitor_mutex));
recog_words_mutex);
// Wait for all threads to complete and aggregate results
for (auto &f : futures) {

View File

@ -79,7 +79,7 @@ void Tesseract::fix_fuzzy_spaces(ETEXT_DESC *monitor, int32_t word_count, PAGE_R
ROW_RES_IT row_res_it;
WERD_RES_IT word_res_it_from;
WERD_RES_IT word_res_it_to;
WERD_RES *word_res;
std::shared_ptr<WERD_RES> word_res;
WERD_RES_LIST fuzzy_space_words;
int16_t new_length;
bool prevent_null_wd_fixsp; // DON'T process blobless wds
@ -114,7 +114,7 @@ void Tesseract::fix_fuzzy_spaces(ETEXT_DESC *monitor, int32_t word_count, PAGE_R
if (!word_res_it_from.at_last()) {
word_res_it_to = word_res_it_from;
prevent_null_wd_fixsp = word_res->word->cblob_list()->empty();
if (check_debug_pt(word_res, 60)) {
if (check_debug_pt(word_res.get(), 60)) {
debug_fix_space_level.set_value(10);
}
word_res_it_to.forward();
@ -131,7 +131,7 @@ void Tesseract::fix_fuzzy_spaces(ETEXT_DESC *monitor, int32_t word_count, PAGE_R
while (!word_res_it_to.at_last() &&
(word_res_it_to.data_relative(1)->word->flag(W_FUZZY_NON) ||
word_res_it_to.data_relative(1)->word->flag(W_FUZZY_SP))) {
if (check_debug_pt(word_res, 60)) {
if (check_debug_pt(word_res.get(), 60)) {
debug_fix_space_level.set_value(10);
}
if (word_res->word->cblob_list()->empty()) {
@ -139,7 +139,7 @@ void Tesseract::fix_fuzzy_spaces(ETEXT_DESC *monitor, int32_t word_count, PAGE_R
}
word_res = word_res_it_to.forward();
}
if (check_debug_pt(word_res, 60)) {
if (check_debug_pt(word_res.get(), 60)) {
debug_fix_space_level.set_value(10);
}
if (word_res->word->cblob_list()->empty()) {
@ -203,12 +203,12 @@ void initialise_search(WERD_RES_LIST &src_list, WERD_RES_LIST &new_list) {
WERD_RES *new_wd;
for (src_it.mark_cycle_pt(); !src_it.cycled_list(); src_it.forward()) {
WERD_RES *src_wd = src_it.data();
WERD_RES *src_wd = src_it.data().get();
if (!src_wd->combination) {
new_wd = WERD_RES::deep_copy(src_wd);
new_wd->combination = false;
new_wd->part_of_combo = false;
new_it.add_after_then_move(new_wd);
new_it.add_after_then_move(std::shared_ptr<WERD_RES>(new_wd));
}
}
}
@ -220,7 +220,7 @@ void Tesseract::match_current_words(WERD_RES_LIST &words, ROW *row, BLOCK *block
// prev_word_best_choice_ before calling classify_word_pass2().
prev_word_best_choice_ = nullptr;
for (word_it.mark_cycle_pt(); !word_it.cycled_list(); word_it.forward()) {
word = word_it.data();
word = word_it.data().get();
if ((!word->part_of_combo) && (word->box_word == nullptr)) {
WordData word_data(block, row, word);
SetupWordPassN(2, &word_data);
@ -269,7 +269,7 @@ int16_t Tesseract::eval_word_spacing(WERD_RES_LIST &word_res_list) {
const char *punct_chars = "!\"`',.:;";
do {
// current word
WERD_RES *word = word_res_it.data();
WERD_RES *word = word_res_it.data().get();
bool word_done = fixspace_thinks_word_done(word);
word_count++;
if (word->tess_failed) {
@ -396,7 +396,7 @@ void transform_to_next_perm(WERD_RES_LIST &words) {
int16_t min_gap = INT16_MAX;
for (word_it.mark_cycle_pt(); !word_it.cycled_list(); word_it.forward()) {
word = word_it.data();
word = word_it.data().get();
if (!word->part_of_combo) {
box = word->word->bounding_box();
if (prev_right > -INT16_MAX) {
@ -413,13 +413,13 @@ void transform_to_next_perm(WERD_RES_LIST &words) {
word_it.set_to_list(&words);
// Note: we can't use cycle_pt due to inserted combos at start of list.
for (; (prev_right == -INT16_MAX) || !word_it.at_first(); word_it.forward()) {
word = word_it.data();
word = word_it.data().get();
if (!word->part_of_combo) {
box = word->word->bounding_box();
if (prev_right > -INT16_MAX) {
gap = box.left() - prev_right;
if (gap <= min_gap) {
prev_word = prev_word_it.data();
prev_word = prev_word_it.data().get();
WERD_RES *combo;
if (prev_word->combination) {
combo = prev_word;
@ -433,14 +433,14 @@ void transform_to_next_perm(WERD_RES_LIST &words) {
combo->combination = true;
combo->x_height = prev_word->x_height;
prev_word->part_of_combo = true;
prev_word_it.add_before_then_move(combo);
prev_word_it.add_before_then_move(std::shared_ptr<WERD_RES>(combo));
}
combo->word->set_flag(W_EOL, word->word->flag(W_EOL));
if (word->combination) {
combo->word->join_on(word->word);
// Move blobs to combo
// old combo no longer needed
delete word_it.extract();
word_it.extract();
} else {
// Copy current wd to combo
combo->copy_on(word);
@ -545,7 +545,7 @@ void Tesseract::fix_sp_fp_word(WERD_RES_IT &word_res_it, ROW *row, BLOCK *block)
int16_t new_length;
float junk;
word_res = word_res_it.data();
word_res = word_res_it.data().get();
if (word_res->word->flag(W_REP_CHAR) || word_res->combination || word_res->part_of_combo ||
!word_res->word->flag(W_DONT_CHOP)) {
return;
@ -582,11 +582,11 @@ void Tesseract::fix_noisy_space_list(WERD_RES_LIST &best_perm, ROW *row, BLOCK *
dump_words(best_perm, best_score, 1, improved);
old_word_res = best_perm_it.data();
old_word_res = best_perm_it.data().get();
// Even deep_copy doesn't copy the underlying WERD unless its combination
// flag is true!.
old_word_res->combination = true; // Kludge to force deep copy
current_perm_it.add_to_end(WERD_RES::deep_copy(old_word_res));
current_perm_it.add_to_end(std::shared_ptr<WERD_RES>(WERD_RES::deep_copy(old_word_res)));
old_word_res->combination = false; // Undo kludge
break_noisiest_blob_word(current_perm);
@ -630,7 +630,7 @@ void Tesseract::break_noisiest_blob_word(WERD_RES_LIST &words) {
int16_t i;
for (word_it.mark_cycle_pt(); !word_it.cycled_list(); word_it.forward()) {
auto blob_index = worst_noise_blob(word_it.data(), &noise_score);
auto blob_index = worst_noise_blob(word_it.data().get(), &noise_score);
if (blob_index > -1 && worst_noise_score > noise_score) {
worst_noise_score = noise_score;
worst_blob_index = blob_index;
@ -644,7 +644,7 @@ void Tesseract::break_noisiest_blob_word(WERD_RES_LIST &words) {
/* Now split the worst_word_it */
word_res = worst_word_it.data();
word_res = worst_word_it.data().get();
/* Move blobs before noise blob to a new bloblist */
@ -671,7 +671,7 @@ void Tesseract::break_noisiest_blob_word(WERD_RES_LIST &words) {
auto *new_word_res = new WERD_RES(new_word);
new_word_res->combination = true;
worst_word_it.add_before_then_move(new_word_res);
worst_word_it.add_before_then_move(std::shared_ptr<WERD_RES>(new_word_res));
word_res->ClearResults();
}
@ -834,7 +834,7 @@ int16_t Tesseract::fp_eval_word_spacing(WERD_RES_LIST &word_res_list) {
float small_limit = kBlnXHeight * fixsp_small_outlines_size;
for (word_it.mark_cycle_pt(); !word_it.cycled_list(); word_it.forward()) {
word = word_it.data();
word = word_it.data().get();
if (word->rebuild_word == nullptr) {
continue; // Can't handle cube words.
}

View File

@ -387,7 +387,7 @@ public:
bool RecogWordsSegment(std::vector<WordData>::iterator start, std::vector<WordData>::iterator end,
int pass_n, ETEXT_DESC *monitor, PAGE_RES *page_res,
LSTMRecognizer *lstm_recognizer, std::atomic<int>& words_done,
int total_words, std::mutex& monitor_mutex);
int total_words, std::shared_ptr<std::mutex> recog_words_mutex);
// Runs word recognition on all the words.
bool RecogAllWordsPassN(int pass_n, ETEXT_DESC *monitor, PAGE_RES *page_res,
std::vector<WordData> *words);

View File

@ -173,18 +173,18 @@ ROW_RES::ROW_RES(bool merge_similar_words, ROW *the_row) {
combo = new WERD_RES(copy_word);
combo->x_height = the_row->x_height();
combo->combination = true;
word_res_it.add_to_end(combo);
word_res_it.add_to_end(std::shared_ptr<WERD_RES>(combo));
}
word_res->part_of_combo = true;
} else {
combo = nullptr;
}
word_res_it.add_to_end(word_res);
word_res_it.add_to_end(std::shared_ptr<WERD_RES>(word_res));
}
}
WERD_RES &WERD_RES::operator=(const WERD_RES &source) {
this->ELIST_LINK::operator=(source);
this->ELIST_LINK_SP::operator=(source);
Clear();
if (source.combination) {
word = new WERD;
@ -1251,6 +1251,7 @@ int PAGE_RES_IT::cmp(const PAGE_RES_IT &other) const {
// with best_choice etc.
WERD_RES *PAGE_RES_IT::InsertSimpleCloneWord(const WERD_RES &clone_res,
WERD *new_word) {
std::lock_guard<std::mutex> guard(*mutex_res);
// Make a WERD_RES for the new_word.
auto *new_res = new WERD_RES(new_word);
new_res->CopySimpleFields(clone_res);
@ -1258,13 +1259,13 @@ WERD_RES *PAGE_RES_IT::InsertSimpleCloneWord(const WERD_RES &clone_res,
// Insert into the appropriate place in the ROW_RES.
WERD_RES_IT wr_it(&row()->word_res_list);
for (wr_it.mark_cycle_pt(); !wr_it.cycled_list(); wr_it.forward()) {
WERD_RES *word = wr_it.data();
if (word == word_res) {
WERD_RES *word = wr_it.data().get();
if (word == word_res.get()) {
break;
}
}
ASSERT_HOST(!wr_it.cycled_list());
wr_it.add_before_then_move(new_res);
wr_it.add_before_then_move(std::shared_ptr<WERD_RES>(new_res));
if (wr_it.at_first()) {
// This is the new first word, so reset the member iterator so it
// detects the cycled_list state correctly.
@ -1381,6 +1382,7 @@ void PAGE_RES_IT::ReplaceCurrentWord(
DeleteCurrentWord();
return;
}
std::lock_guard<std::mutex> guard(*mutex_res);
WERD_RES *input_word = word();
// Set the BOL/EOL flags on the words from the input word.
if (input_word->word->flag(W_BOL)) {
@ -1410,7 +1412,7 @@ void PAGE_RES_IT::ReplaceCurrentWord(
// Insert into the appropriate place in the ROW_RES.
WERD_RES_IT wr_it(&row()->word_res_list);
for (wr_it.mark_cycle_pt(); !wr_it.cycled_list(); wr_it.forward()) {
WERD_RES *word = wr_it.data();
WERD_RES *word = wr_it.data().get();
if (word == input_word) {
break;
}
@ -1470,7 +1472,7 @@ void PAGE_RES_IT::ReplaceCurrentWord(
word_w->combination = false;
}
(*words)[w] = nullptr; // We are taking ownership.
wr_it.add_before_stay_put(word_w);
wr_it.add_before_stay_put(std::shared_ptr<WERD_RES>(word_w));
}
// We have taken ownership of the words.
words->clear();
@ -1480,12 +1482,13 @@ void PAGE_RES_IT::ReplaceCurrentWord(
if (!input_word->combination) {
delete w_it.extract();
}
delete wr_it.extract();
wr_it.extract_mt();
ResetWordIterator();
}
// Deletes the current WERD_RES and its underlying WERD.
void PAGE_RES_IT::DeleteCurrentWord() {
std::lock_guard<std::mutex> guard(*mutex_res);
// Check that this word is as we expect. part_of_combos are NEVER iterated
// by the normal iterator, so we should never be trying to delete them.
ASSERT_HOST(!word_res->part_of_combo);
@ -1512,13 +1515,14 @@ void PAGE_RES_IT::DeleteCurrentWord() {
}
}
ASSERT_HOST(!wr_it.cycled_list());
delete wr_it.extract();
wr_it.extract_mt();
ResetWordIterator();
}
// Makes the current word a fuzzy space if not already fuzzy. Updates
// corresponding part of combo if required.
void PAGE_RES_IT::MakeCurrentWordFuzzy() {
std::lock_guard<std::mutex> guard(*mutex_res);
WERD *real_word = word_res->word;
if (!real_word->flag(W_FUZZY_SP) && !real_word->flag(W_FUZZY_NON)) {
real_word->set_flag(W_FUZZY_SP, true);
@ -1583,7 +1587,10 @@ void PAGE_RES_IT::ResetWordIterator() {
word_res = word_res_it.data();
}
}
ASSERT_HOST(!word_res_it.cycled_list());
if (word_res_it.cycled_list()) {
// We didn't find next_word_res. Maybe it has been deleted by some other thread.
return;
}
wr_it_of_next_word = word_res_it;
word_res_it.forward();
} else {
@ -1672,7 +1679,7 @@ foundword:
? nullptr
: prev_word_res->best_choice;
}
return word_res;
return word_res.get();
}
/*************************************************************************
@ -1698,6 +1705,7 @@ WERD_RES *PAGE_RES_IT::restart_row() {
*************************************************************************/
WERD_RES *PAGE_RES_IT::forward_paragraph() {
std::lock_guard<std::mutex> guard(*mutex_res);
while (block_res == next_block_res &&
(next_row_res != nullptr && next_row_res->row != nullptr &&
row_res->row->para() == next_row_res->row->para())) {
@ -1713,12 +1721,21 @@ WERD_RES *PAGE_RES_IT::forward_paragraph() {
*************************************************************************/
WERD_RES *PAGE_RES_IT::forward_block() {
std::lock_guard<std::mutex> guard(*mutex_res);
while (block_res == next_block_res) {
internal_forward(false, true);
}
return internal_forward(false, true);
}
bool PAGE_RES_IT::forward_to_word(const WERD_RES* word_res) {
std::lock_guard<std::mutex> guard(*mutex_res);
while (word() != nullptr && word() != word_res) {
internal_forward(false, false);
}
return word() != nullptr;
}
void PAGE_RES_IT::rej_stat_word() {
int16_t chars_in_word;
int16_t rejects_in_word = 0;

View File

@ -37,6 +37,7 @@
#include <functional> // for std::function
#include <set> // for std::pair
#include <vector> // for std::vector
#include <memory> // for std::shared_ptr
#include <sys/types.h> // for int8_t
@ -69,7 +70,7 @@ class ROW_RES;
ELISTIZEH(ROW_RES)
class WERD_RES;
ELISTIZEH(WERD_RES)
ELISTIZEH_SP(WERD_RES)
/*************************************************************************
* PAGE_RES - Page results
@ -161,7 +162,7 @@ enum CRUNCH_MODE { CR_NONE, CR_KEEP_SPACE, CR_LOOSE_SPACE, CR_DELETE };
// WERD_RES is a collection of publicly accessible members that gathers
// information about a word result.
class TESS_API WERD_RES : public ELIST_LINK {
class TESS_API WERD_RES : public ELIST_LINK_SP {
public:
// Which word is which?
// There are 3 coordinate spaces in use here: a possibly rotated pixel space,
@ -345,7 +346,7 @@ public:
}
// Deep copies everything except the ratings MATRIX.
// To get that use deep_copy below.
WERD_RES(const WERD_RES &source) : ELIST_LINK(source) {
WERD_RES(const WERD_RES &source) : ELIST_LINK_SP(source) {
// combination is used in function Clear which is called from operator=.
combination = false;
*this = source; // see operator=
@ -685,8 +686,11 @@ public:
PAGE_RES_IT() = default;
PAGE_RES_IT(PAGE_RES *the_page_res) { // page result
// If multiple instances of PAGE_RES_IT needs to be used concurrently,
// then they must be created using the same mutex_res.
PAGE_RES_IT(PAGE_RES *the_page_res, std::shared_ptr<std::mutex> the_mutex_res = std::make_shared<std::mutex>()) { // page result
page_res = the_page_res;
mutex_res = the_mutex_res;
restart_page(); // ready to scan
}
@ -720,8 +724,8 @@ public:
// ============ Methods that mutate the underling structures ===========
// Note that these methods will potentially invalidate other PAGE_RES_ITs
// and are intended to be used only while a single PAGE_RES_IT is active.
// This problem needs to be taken into account if these mutation operators
// are ever provided to PageIterator or its subclasses.
// To use these methods safely, from multiple PAGE_RES_ITs concurrently, it
// is needed to construct all these iterators with the same mutex_res.
// Inserts the new_word and a corresponding WERD_RES before the current
// position. The simple fields of the WERD_RES are copied from clone_res and
@ -741,18 +745,22 @@ public:
void MakeCurrentWordFuzzy();
WERD_RES *forward() { // Get next word.
std::lock_guard<std::mutex> guard(*mutex_res);
return internal_forward(false, false);
}
// Move forward, but allow empty blocks to show as single nullptr words.
WERD_RES *forward_with_empties() {
std::lock_guard<std::mutex> guard(*mutex_res);
return internal_forward(false, true);
}
WERD_RES *forward_paragraph(); // get first word in next non-empty paragraph
WERD_RES *forward_block(); // get first word in next non-empty block
bool forward_to_word(const WERD_RES *word_res); // go to this word
WERD_RES *prev_word() const { // previous word
return prev_word_res;
return prev_word_res.get();
}
ROW_RES *prev_row() const { // row of prev word
return prev_row_res;
@ -761,7 +769,7 @@ public:
return prev_block_res;
}
WERD_RES *word() const { // current word
return word_res;
return word_res.get();
}
ROW_RES *row() const { // row of current word
return row_res;
@ -770,7 +778,7 @@ public:
return block_res;
}
WERD_RES *next_word() const { // next word
return next_word_res;
return next_word_res.get();
}
ROW_RES *next_row() const { // row of next word
return next_row_res;
@ -784,15 +792,15 @@ public:
private:
WERD_RES *internal_forward(bool new_block, bool empty_ok);
WERD_RES *prev_word_res; // previous word
std::shared_ptr<WERD_RES> prev_word_res; // previous word
ROW_RES *prev_row_res; // row of prev word
BLOCK_RES *prev_block_res; // block of prev word
WERD_RES *word_res; // current word
std::shared_ptr<WERD_RES> word_res; // current word
ROW_RES *row_res; // row of current word
BLOCK_RES *block_res; // block of cur. word
WERD_RES *next_word_res; // next word
std::shared_ptr<WERD_RES> next_word_res; // next word
ROW_RES *next_row_res; // row of next word
BLOCK_RES *next_block_res; // block of next word
@ -803,6 +811,8 @@ private:
// Since word_res_it is 2 words further on, this is otherwise hard to do.
WERD_RES_IT wr_it_of_current_word;
WERD_RES_IT wr_it_of_next_word;
std::shared_ptr<std::mutex> mutex_res;
};
} // namespace tesseract

View File

@ -33,11 +33,12 @@ namespace tesseract {
* the consequential memory overhead.
**********************************************************************/
void ELIST::internal_clear( // destroy all links
template <typename T>
inline void ELIST_T<T>::internal_clear( // destroy all links
void (*zapper)(void *)) {
// ptr to zapper functn
ELIST_LINK *ptr;
ELIST_LINK *next;
T ptr;
T next;
if (!empty()) {
ptr = last->next; // set to first
@ -45,7 +46,10 @@ void ELIST::internal_clear( // destroy all links
last = nullptr; // set list empty
while (ptr) {
next = ptr->next;
zapper(ptr);
if constexpr (!is_shared_ptr<T>::value) {
// We need not delete the shared pointer objects as it will be deleted automatically
zapper(ptr);
}
ptr = next;
}
}
@ -64,9 +68,10 @@ void ELIST::internal_clear( // destroy all links
* end point is always the end_it position.
**********************************************************************/
void ELIST::assign_to_sublist( // to this list
ELIST_ITERATOR *start_it, // from list start
ELIST_ITERATOR *end_it) { // from list end
template <typename T>
void ELIST_T<T>::assign_to_sublist( // to this list
ELIST_ITERATOR_T<T> *start_it, // from list start
ELIST_ITERATOR_T<T> *end_it) { // from list end
constexpr ERRCODE LIST_NOT_EMPTY("Destination list must be empty before extracting a sublist");
if (!empty()) {
@ -84,7 +89,8 @@ void ELIST::assign_to_sublist( // to this list
* ( int (*)(const void *, const void *)
**********************************************************************/
void ELIST::sort( // sort elements
template <typename T>
void ELIST_T<T>::sort( // sort elements
int comparator( // comparison routine
const void *, const void *)) {
// Allocate an array of pointers, one per list element.
@ -92,10 +98,10 @@ void ELIST::sort( // sort elements
if (count > 0) {
// ptr array to sort
std::vector<ELIST_LINK *> base;
std::vector<T> base;
base.reserve(count);
ELIST_ITERATOR it(this);
ELIST_ITERATOR_T<T> it(this);
// Extract all elements, putting the pointers in the array.
for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
@ -122,8 +128,9 @@ void ELIST::sort( // sort elements
// list) - new_link is not added to the list and the function returns the
// pointer to the identical entry that already exists in the list
// (otherwise the function returns new_link).
ELIST_LINK *ELIST::add_sorted_and_find(int comparator(const void *, const void *), bool unique,
ELIST_LINK *new_link) {
template <typename T>
T ELIST_T<T>::add_sorted_and_find(int comparator(const void *, const void *), bool unique,
T new_link) {
// Check for adding at the end.
if (last == nullptr || comparator(&last, &new_link) < 0) {
if (last == nullptr) {
@ -135,9 +142,9 @@ ELIST_LINK *ELIST::add_sorted_and_find(int comparator(const void *, const void *
last = new_link;
} else {
// Need to use an iterator.
ELIST_ITERATOR it(this);
ELIST_ITERATOR_T<T> it(this);
for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
ELIST_LINK *link = it.data();
T link = it.data();
int compare = comparator(&link, &new_link);
if (compare > 0) {
break;
@ -165,8 +172,8 @@ ELIST_LINK *ELIST::add_sorted_and_find(int comparator(const void *, const void *
* Move the iterator to the next element of the list.
* REMEMBER: ALL LISTS ARE CIRCULAR.
**********************************************************************/
ELIST_LINK *ELIST_ITERATOR::forward() {
template <typename T>
inline T ELIST_ITERATOR_T<T>::forward() {
#ifndef NDEBUG
if (!list)
NO_LIST.error("ELIST_ITERATOR::forward", ABORT);
@ -195,10 +202,17 @@ ELIST_LINK *ELIST_ITERATOR::forward() {
#ifndef NDEBUG
if (!next) {
void *current_ptr = nullptr;
if constexpr(is_shared_ptr<T>::value) {
current_ptr = static_cast<void *>(current.get());
}
else {
current_ptr = static_cast<void *>(current);
}
NULL_NEXT.error("ELIST_ITERATOR::forward", ABORT,
"This is: %p Current is: %p",
static_cast<void *>(this),
static_cast<void *>(current));
current_ptr);
}
#endif
return current;
@ -212,9 +226,10 @@ ELIST_LINK *ELIST_ITERATOR::forward() {
* (This function can't be INLINEd because it contains a loop)
**********************************************************************/
ELIST_LINK *ELIST_ITERATOR::data_relative( // get data + or - ...
template <typename T>
T ELIST_ITERATOR_T<T>::data_relative( // get data + or - ...
int8_t offset) { // offset from current
ELIST_LINK *ptr;
T ptr;
#ifndef NDEBUG
if (!list)
@ -249,7 +264,8 @@ ELIST_LINK *ELIST_ITERATOR::data_relative( // get data + or - ...
* (This function can't be INLINEd because it contains a loop)
**********************************************************************/
ELIST_LINK *ELIST_ITERATOR::move_to_last() {
template <typename T>
T ELIST_ITERATOR_T<T>::move_to_last() {
#ifndef NDEBUG
if (!list)
NO_LIST.error("ELIST_ITERATOR::move_to_last", ABORT);
@ -272,11 +288,12 @@ ELIST_LINK *ELIST_ITERATOR::move_to_last() {
* (This function hasn't been in-lined because its a bit big!)
**********************************************************************/
void ELIST_ITERATOR::exchange( // positions of 2 links
ELIST_ITERATOR *other_it) { // other iterator
template <typename T>
void ELIST_ITERATOR_T<T>::exchange( // positions of 2 links
ELIST_ITERATOR_T<T> *other_it) { // other iterator
constexpr ERRCODE DONT_EXCHANGE_DELETED("Can't exchange deleted elements of lists");
ELIST_LINK *old_current;
T old_current;
#ifndef NDEBUG
if (!list)
@ -368,16 +385,17 @@ non-adjacent elements. */
* (Can't inline this function because it contains a loop)
**********************************************************************/
ELIST_LINK *ELIST_ITERATOR::extract_sublist( // from this current
ELIST_ITERATOR *other_it) { // to other current
template <typename T>
T ELIST_ITERATOR_T<T>::extract_sublist( // from this current
ELIST_ITERATOR_T<T> *other_it) { // to other current
#ifndef NDEBUG
constexpr ERRCODE BAD_EXTRACTION_PTS("Can't extract sublist from points on different lists");
constexpr ERRCODE DONT_EXTRACT_DELETED("Can't extract a sublist marked by deleted points");
#endif
constexpr ERRCODE BAD_SUBLIST("Can't find sublist end point in original list");
ELIST_ITERATOR temp_it = *this;
ELIST_LINK *end_of_new_list;
ELIST_ITERATOR_T<T> temp_it = *this;
T end_of_new_list;
#ifndef NDEBUG
if (!other_it)
@ -437,4 +455,12 @@ ELIST_LINK *ELIST_ITERATOR::extract_sublist( // from this current
return end_of_new_list;
}
template class ELIST_LINK_T<ELIST_LINK*>;
template class ELIST_T<ELIST_LINK*>;
template class ELIST_ITERATOR_T<ELIST_LINK*>;
template class ELIST_LINK_T<std::shared_ptr<ELIST_LINK_SP>>;
template class ELIST_T<std::shared_ptr< ELIST_LINK_SP>>;
template class ELIST_ITERATOR_T<std::shared_ptr<ELIST_LINK_SP>>;
} // namespace tesseract

View File

@ -24,11 +24,10 @@
#include "serialis.h"
#include <cstdio>
#include <memory>
namespace tesseract {
class ELIST_ITERATOR;
/**********************************************************************
This module implements list classes and iterators.
The following list types and iterators are provided:
@ -79,25 +78,43 @@ lists.
* walks the list.
**********************************************************************/
class ELIST_LINK {
friend class ELIST_ITERATOR;
friend class ELIST;
// Forward declarations
template<typename T>
class ELIST_ITERATOR_T;
ELIST_LINK *next;
template<typename T>
class ELIST_LINK_T;
template<typename T>
class ELIST_T;
template <typename U>
struct is_shared_ptr : std::false_type {};
template <typename U>
struct is_shared_ptr<std::shared_ptr<U>> : std::true_type {};
template<typename T>
class ELIST_LINK_T {
friend class ELIST_ITERATOR_T<T>;
friend class ELIST_T<T>;
protected:
T next;
public:
ELIST_LINK() {
ELIST_LINK_T() {
next = nullptr;
}
// constructor
// The special copy constructor is used by lots of classes.
ELIST_LINK(const ELIST_LINK &) {
ELIST_LINK_T(const ELIST_LINK_T &) {
next = nullptr;
}
// The special assignment operator is used by lots of classes.
void operator=(const ELIST_LINK &) {
void operator=(const ELIST_LINK_T &) {
next = nullptr;
}
};
@ -108,16 +125,19 @@ public:
* Generic list class for singly linked lists with embedded links
**********************************************************************/
class TESS_API ELIST {
friend class ELIST_ITERATOR;
ELIST_LINK *last = nullptr; // End of list
template<typename T>
class TESS_API ELIST_T {
friend class ELIST_ITERATOR_T<T>;
protected:
T last; // End of list
//(Points to head)
ELIST_LINK *First() { // return first
T First() { // return first
return last ? last->next : nullptr;
}
public:
ELIST_T(): last(nullptr) { // constructor
}
// destroy all links
void internal_clear(void (*zapper)(void *));
@ -130,17 +150,13 @@ public:
}
void shallow_copy( // dangerous!!
ELIST *from_list) { // beware destructors!!
ELIST_T *from_list) { // beware destructors!!
last = from_list->last;
}
// ptr to copier functn
void internal_deep_copy(ELIST_LINK *(*copier)(ELIST_LINK *),
const ELIST *list); // list being copied
void assign_to_sublist( // to this list
ELIST_ITERATOR *start_it, // from list start
ELIST_ITERATOR *end_it); // from list end
ELIST_ITERATOR_T<T> *start_it, // from list start
ELIST_ITERATOR_T<T> *end_it); // from list end
// # elements in list
int32_t length() const {
@ -168,12 +184,12 @@ public:
// list) - new_link is not added to the list and the function returns the
// pointer to the identical entry that already exists in the list
// (otherwise the function returns new_link).
ELIST_LINK *add_sorted_and_find(int comparator(const void *, const void *), bool unique,
ELIST_LINK *new_link);
T add_sorted_and_find(int comparator(const void *, const void *), bool unique,
T new_link);
// Same as above, but returns true if the new entry was inserted, false
// if the identical entry already existed in the list.
bool add_sorted(int comparator(const void *, const void *), bool unique, ELIST_LINK *new_link) {
bool add_sorted(int comparator(const void *, const void *), bool unique, T new_link) {
return (add_sorted_and_find(comparator, unique, new_link) == new_link);
}
};
@ -185,50 +201,52 @@ public:
*embedded links
**********************************************************************/
class TESS_API ELIST_ITERATOR {
friend void ELIST::assign_to_sublist(ELIST_ITERATOR *, ELIST_ITERATOR *);
ELIST *list; // List being iterated
ELIST_LINK *prev; // prev element
ELIST_LINK *current; // current element
ELIST_LINK *next; // next element
ELIST_LINK *cycle_pt; // point we are cycling the list to.
template<typename T>
class TESS_API ELIST_ITERATOR_T {
friend void ELIST_T<T>::assign_to_sublist(ELIST_ITERATOR_T *, ELIST_ITERATOR_T *);
protected:
ELIST_T<T> *list; // List being iterated
T prev; // prev element
T current; // current element
T next; // next element
T cycle_pt; // point we are cycling the list to.
bool ex_current_was_last; // current extracted was end of list
bool ex_current_was_cycle_pt; // current extracted was cycle point
bool started_cycling; // Have we moved off the start?
ELIST_LINK *extract_sublist( // from this current...
ELIST_ITERATOR *other_it); // to other current
T extract_sublist( // from this current...
ELIST_ITERATOR_T<T> *other_it); // to other current
private:
T extract_internal();
public:
ELIST_ITERATOR() { // constructor
ELIST_ITERATOR_T() { // constructor
list = nullptr;
} // unassigned list
explicit ELIST_ITERATOR(ELIST *list_to_iterate);
explicit ELIST_ITERATOR_T(ELIST_T<T> *list_to_iterate);
void set_to_list( // change list
ELIST *list_to_iterate);
ELIST_T<T> *list_to_iterate);
void add_after_then_move( // add after current &
ELIST_LINK *new_link); // move to new
T new_link); // move to new
void add_after_stay_put( // add after current &
ELIST_LINK *new_link); // stay at current
T new_link); // stay at current
void add_before_then_move( // add before current &
ELIST_LINK *new_link); // move to new
T new_link); // move to new
void add_before_stay_put( // add before current &
ELIST_LINK *new_link); // stay at current
T new_link); // stay at current
void add_list_after( // add a list &
ELIST *list_to_add); // stay at current
ELIST_T<T> *list_to_add); // stay at current
void add_list_before( // add a list &
ELIST *list_to_add); // move to it 1st item
ELIST_T<T> *list_to_add); // move to it 1st item
ELIST_LINK *data() { // get current data
T data() { // get current data
#ifndef NDEBUG
if (!list) {
NO_LIST.error("ELIST_ITERATOR::data", ABORT);
@ -240,16 +258,17 @@ public:
return current;
}
ELIST_LINK *data_relative( // get data + or - ...
T data_relative( // get data + or - ...
int8_t offset); // offset from current
ELIST_LINK *forward(); // move to next element
T forward(); // move to next element
ELIST_LINK *extract(); // remove from list
T extract(); // remove from list
T extract_mt(); // remove from list (multithreaded version)
ELIST_LINK *move_to_first(); // go to start of list
T move_to_first(); // go to start of list
ELIST_LINK *move_to_last(); // go to end of list
T move_to_last(); // go to end of list
void mark_cycle_pt(); // remember current
@ -263,7 +282,7 @@ public:
}
bool current_extracted() const { // current extracted?
return !current;
return current == nullptr;
}
bool at_first() const; // Current is first?
@ -273,10 +292,10 @@ public:
bool cycled_list() const; // Completed a cycle?
void add_to_end( // add at end &
ELIST_LINK *new_link); // don't move
T new_link); // don't move
void exchange( // positions of 2 links
ELIST_ITERATOR *other_it); // other iterator
ELIST_ITERATOR_T *other_it); // other iterator
//# elements in list
int32_t length() const {
@ -295,8 +314,9 @@ public:
* over.
**********************************************************************/
inline void ELIST_ITERATOR::set_to_list( // change list
ELIST *list_to_iterate) {
template<typename T>
inline void ELIST_ITERATOR_T<T>::set_to_list( // change list
ELIST_T<T> *list_to_iterate) {
#ifndef NDEBUG
if (!list_to_iterate) {
BAD_PARAMETER.error("ELIST_ITERATOR::set_to_list", ABORT, "list_to_iterate is nullptr");
@ -319,7 +339,8 @@ inline void ELIST_ITERATOR::set_to_list( // change list
* CONSTRUCTOR - set iterator to specified list;
**********************************************************************/
inline ELIST_ITERATOR::ELIST_ITERATOR(ELIST *list_to_iterate) {
template<typename T>
inline ELIST_ITERATOR_T<T>::ELIST_ITERATOR_T(ELIST_T<T> *list_to_iterate) {
set_to_list(list_to_iterate);
}
@ -330,8 +351,9 @@ inline ELIST_ITERATOR::ELIST_ITERATOR(ELIST *list_to_iterate) {
* iterator to the new element.
**********************************************************************/
inline void ELIST_ITERATOR::add_after_then_move( // element to add
ELIST_LINK *new_element) {
template<typename T>
inline void ELIST_ITERATOR_T<T>::add_after_then_move( // element to add
T new_element) {
#ifndef NDEBUG
if (!list) {
NO_LIST.error("ELIST_ITERATOR::add_after_then_move", ABORT);
@ -377,8 +399,9 @@ inline void ELIST_ITERATOR::add_after_then_move( // element to add
* the iterator to the new element.
**********************************************************************/
inline void ELIST_ITERATOR::add_after_stay_put( // element to add
ELIST_LINK *new_element) {
template<typename T>
inline void ELIST_ITERATOR_T<T>::add_after_stay_put( // element to add
T new_element) {
#ifndef NDEBUG
if (!list) {
NO_LIST.error("ELIST_ITERATOR::add_after_stay_put", ABORT);
@ -426,8 +449,9 @@ inline void ELIST_ITERATOR::add_after_stay_put( // element to add
* iterator to the new element.
**********************************************************************/
inline void ELIST_ITERATOR::add_before_then_move( // element to add
ELIST_LINK *new_element) {
template<typename T>
inline void ELIST_ITERATOR_T<T>::add_before_then_move( // element to add
T new_element) {
#ifndef NDEBUG
if (!list) {
NO_LIST.error("ELIST_ITERATOR::add_before_then_move", ABORT);
@ -469,8 +493,9 @@ inline void ELIST_ITERATOR::add_before_then_move( // element to add
* iterator to the new element.
**********************************************************************/
inline void ELIST_ITERATOR::add_before_stay_put( // element to add
ELIST_LINK *new_element) {
template<typename T>
inline void ELIST_ITERATOR_T<T>::add_before_stay_put( // element to add
T new_element) {
#ifndef NDEBUG
if (!list) {
NO_LIST.error("ELIST_ITERATOR::add_before_stay_put", ABORT);
@ -514,7 +539,8 @@ inline void ELIST_ITERATOR::add_before_stay_put( // element to add
* iterator.
**********************************************************************/
inline void ELIST_ITERATOR::add_list_after(ELIST *list_to_add) {
template<typename T>
inline void ELIST_ITERATOR_T<T>::add_list_after(ELIST_T<T> *list_to_add) {
#ifndef NDEBUG
if (!list) {
NO_LIST.error("ELIST_ITERATOR::add_list_after", ABORT);
@ -561,7 +587,8 @@ inline void ELIST_ITERATOR::add_list_after(ELIST *list_to_add) {
* iterator.
**********************************************************************/
inline void ELIST_ITERATOR::add_list_before(ELIST *list_to_add) {
template<typename T>
inline void ELIST_ITERATOR_T<T>::add_list_before(ELIST_T<T> *list_to_add) {
#ifndef NDEBUG
if (!list) {
NO_LIST.error("ELIST_ITERATOR::add_list_before", ABORT);
@ -607,8 +634,9 @@ inline void ELIST_ITERATOR::add_list_before(ELIST *list_to_add) {
* is to be deleted, this is the callers responsibility.
**********************************************************************/
inline ELIST_LINK *ELIST_ITERATOR::extract() {
ELIST_LINK *extracted_link;
template<typename T>
inline T ELIST_ITERATOR_T<T>::extract_internal() {
T extracted_link;
#ifndef NDEBUG
if (!list) {
@ -634,11 +662,23 @@ inline ELIST_LINK *ELIST_ITERATOR::extract() {
// Always set ex_current_was_cycle_pt so an add/forward will work in a loop.
ex_current_was_cycle_pt = (current == cycle_pt);
extracted_link = current;
extracted_link->next = nullptr; // for safety
current = nullptr;
return extracted_link;
}
template<typename T>
inline T ELIST_ITERATOR_T<T>::extract() {
T extracted_link = extract_internal();
extracted_link->next = nullptr; // for safety
return extracted_link;
}
template<typename T>
inline T ELIST_ITERATOR_T<T>::extract_mt() {
return extract_internal();
}
/***********************************************************************
* ELIST_ITERATOR::move_to_first()
*
@ -646,7 +686,8 @@ inline ELIST_LINK *ELIST_ITERATOR::extract() {
* Return data just in case anyone wants it.
**********************************************************************/
inline ELIST_LINK *ELIST_ITERATOR::move_to_first() {
template<typename T>
inline T ELIST_ITERATOR_T<T>::move_to_first() {
#ifndef NDEBUG
if (!list) {
NO_LIST.error("ELIST_ITERATOR::move_to_first", ABORT);
@ -670,7 +711,8 @@ inline ELIST_LINK *ELIST_ITERATOR::move_to_first() {
* by a forward, add_after_then_move or add_after_then_move.
**********************************************************************/
inline void ELIST_ITERATOR::mark_cycle_pt() {
template<typename T>
inline void ELIST_ITERATOR_T<T>::mark_cycle_pt() {
#ifndef NDEBUG
if (!list) {
NO_LIST.error("ELIST_ITERATOR::mark_cycle_pt", ABORT);
@ -692,7 +734,8 @@ inline void ELIST_ITERATOR::mark_cycle_pt() {
*
**********************************************************************/
inline bool ELIST_ITERATOR::at_first() const {
template<typename T>
inline bool ELIST_ITERATOR_T<T>::at_first() const {
#ifndef NDEBUG
if (!list) {
NO_LIST.error("ELIST_ITERATOR::at_first", ABORT);
@ -712,7 +755,8 @@ inline bool ELIST_ITERATOR::at_first() const {
*
**********************************************************************/
inline bool ELIST_ITERATOR::at_last() const {
template<typename T>
inline bool ELIST_ITERATOR_T<T>::at_last() const {
#ifndef NDEBUG
if (!list) {
NO_LIST.error("ELIST_ITERATOR::at_last", ABORT);
@ -732,7 +776,8 @@ inline bool ELIST_ITERATOR::at_last() const {
*
**********************************************************************/
inline bool ELIST_ITERATOR::cycled_list() const {
template<typename T>
inline bool ELIST_ITERATOR_T<T>::cycled_list() const {
#ifndef NDEBUG
if (!list) {
NO_LIST.error("ELIST_ITERATOR::cycled_list", ABORT);
@ -749,7 +794,8 @@ inline bool ELIST_ITERATOR::cycled_list() const {
*
**********************************************************************/
inline void ELIST_ITERATOR::sort( // sort elements
template<typename T>
inline void ELIST_ITERATOR_T<T>::sort( // sort elements
int comparator( // comparison routine
const void *, const void *)) {
#ifndef NDEBUG
@ -772,8 +818,9 @@ inline void ELIST_ITERATOR::sort( // sort elements
queues.
**********************************************************************/
inline void ELIST_ITERATOR::add_to_end( // element to add
ELIST_LINK *new_element) {
template<typename T>
inline void ELIST_ITERATOR_T<T>::add_to_end( // element to add
T new_element) {
#ifndef NDEBUG
if (!list) {
NO_LIST.error("ELIST_ITERATOR::add_to_end", ABORT);
@ -800,6 +847,18 @@ inline void ELIST_ITERATOR::add_to_end( // element to add
}
}
// Type aliases for raw pointer versions
class ELIST_LINK : public ELIST_LINK_T<ELIST_LINK*> {};
using ELIST = ELIST_T<ELIST_LINK*>;
using ELIST_ITERATOR = ELIST_ITERATOR_T<ELIST_LINK*>;
// Type aliases for shared_ptr versions
class ELIST_LINK_SP : public ELIST_LINK_T<std::shared_ptr<ELIST_LINK_SP>> {};
using ELIST_SP = ELIST_T<std::shared_ptr<ELIST_LINK_SP>>;
using ELIST_ITERATOR_SP = ELIST_ITERATOR_T<std::shared_ptr<ELIST_LINK_SP>>;
#define ELISTIZEH(CLASSNAME) \
class CLASSNAME##_LIST : public X_LIST<ELIST, ELIST_ITERATOR, CLASSNAME> { \
using X_LIST<ELIST, ELIST_ITERATOR, CLASSNAME>::X_LIST; \
@ -808,6 +867,15 @@ inline void ELIST_ITERATOR::add_to_end( // element to add
using X_ITER<ELIST_ITERATOR, CLASSNAME>::X_ITER; \
};
#define ELISTIZEH_SP(CLASSNAME) \
class CLASSNAME##_LIST \
: public X_LIST_SP<ELIST_SP, ELIST_ITERATOR_SP, CLASSNAME> { \
using X_LIST_SP<ELIST_SP, ELIST_ITERATOR_SP, CLASSNAME>::X_LIST_SP; \
}; \
class CLASSNAME##_IT : public X_ITER_SP<ELIST_ITERATOR_SP, CLASSNAME> { \
using X_ITER_SP<ELIST_ITERATOR_SP, CLASSNAME>::X_ITER_SP; \
};
} // namespace tesseract
#endif

View File

@ -15,6 +15,7 @@
#define LIST_ITERATOR_H
#include <stdint.h>
#include <memory>
namespace tesseract {
@ -65,6 +66,55 @@ public:
}
};
template <typename ITERATOR, typename CLASSNAME>
class X_ITER_SP : public ITERATOR {
public:
X_ITER_SP() = default;
template <typename U>
X_ITER_SP(U *list) : ITERATOR(list) {}
std::shared_ptr<CLASSNAME> data() {
return std::static_pointer_cast<CLASSNAME>(ITERATOR::data());
}
std::shared_ptr<CLASSNAME> data_relative(int8_t offset) {
return std::static_pointer_cast<CLASSNAME>(ITERATOR::data_relative(offset));
}
std::shared_ptr<CLASSNAME> forward() {
return std::static_pointer_cast<CLASSNAME>(ITERATOR::forward());
}
std::shared_ptr<CLASSNAME> extract() {
return std::static_pointer_cast<CLASSNAME>(ITERATOR::extract());
}
};
template <typename CONTAINER, typename ITERATOR, typename CLASSNAME>
class X_LIST_SP : public CONTAINER {
public:
X_LIST_SP() = default;
X_LIST_SP(const X_LIST_SP &) = delete;
X_LIST_SP &operator=(const X_LIST_SP &) = delete;
~X_LIST_SP() {
clear();
}
/* delete elements */
void clear() {
CONTAINER::internal_clear(
[](void *link) { delete reinterpret_cast<CLASSNAME *>(link); });
}
/* Become a deep copy of src_list */
template <typename U>
void deep_copy(const U *src_list, CLASSNAME *(*copier)(const CLASSNAME *)) {
X_ITER_SP<ITERATOR, CLASSNAME> from_it(const_cast<U *>(src_list));
X_ITER_SP<ITERATOR, CLASSNAME> to_it(this);
for (from_it.mark_cycle_pt(); !from_it.cycled_list(); from_it.forward())
to_it.add_after_then_move(
std::shared_ptr<CLASSNAME>((*copier)(from_it.data().get())));
}
};
} // namespace tesseract
#endif