|
1 /* |
|
2 * Copyright (c) 2007-2009 Nokia Corporation and/or its subsidiary(-ies). |
|
3 * All rights reserved. |
|
4 * This component and the accompanying materials are made available |
|
5 * under the terms of "Eclipse Public License v1.0" |
|
6 * which accompanies this distribution, and is available |
|
7 * at the URL "http://www.eclipse.org/legal/epl-v10.html". |
|
8 * |
|
9 * Initial Contributors: |
|
10 * Nokia Corporation - initial contribution. |
|
11 * |
|
12 * Contributors: |
|
13 * |
|
14 * Description: Functionality of function analysis |
|
15 * |
|
16 */ |
|
17 |
|
18 |
|
19 #include "la.hpp" |
|
20 |
|
21 // ---------------------------------------------------------------------------------------------------------- |
|
22 |
|
23 /** |
|
24 * This flags affects to the function comparison policy. |
|
25 */ |
|
26 // Default policy (=exact match): |
|
27 #define COMPARE_FLAG_NONE 0x00000000 |
|
28 |
|
29 // This flag means that adding a 'const' qualifier to a function |
|
30 // parameter does not generate BBC break issue: |
|
31 #define COMPARE_FLAG_ALLOW_CONST_ADDITION 0x00000001 |
|
32 |
|
33 // ---------------------------------------------------------------------------------------------------------- |
|
34 |
|
35 // Forward declaration |
|
36 class ParameterList; |
|
37 |
|
38 // ParameterElement class represents a parameter element, which has an actual |
|
39 // value and optional sub-parameters. |
|
40 class ParameterElement |
|
41 { |
|
42 public: |
|
43 // Default constructor and destructor |
|
44 ParameterElement(); |
|
45 virtual ~ParameterElement(); |
|
46 |
|
47 // Copy constructor |
|
48 ParameterElement(const ParameterElement& rhs); |
|
49 // Assignment operator |
|
50 const ParameterElement& operator= (const ParameterElement& rhs); |
|
51 |
|
52 // Methods for getting the element value. |
|
53 string& Value() { return val; }; |
|
54 const string& Value() const { return val; }; |
|
55 |
|
56 // Methods for getting the sub-parameters. |
|
57 ParameterList* SubParameters() { return subPars; }; |
|
58 const ParameterList* SubParameters() const { return subPars; }; |
|
59 |
|
60 // Sets the sub-parameters. Takes the ownership to the given |
|
61 // sub-parameter list pointer. |
|
62 void SetSubParameters( ParameterList* pPars ); |
|
63 |
|
64 // Clears the value and sub-parameters. |
|
65 void Clear(); |
|
66 |
|
67 private: |
|
68 string val; |
|
69 ParameterList* subPars; |
|
70 }; |
|
71 |
|
72 // ---------------------------------------------------------------------------------------------------------- |
|
73 |
|
74 typedef vector<ParameterElement> FunctionParameter; |
|
75 |
|
76 class ParameterList : public vector<FunctionParameter> |
|
77 {}; |
|
78 |
|
79 ParameterElement::ParameterElement() |
|
80 { |
|
81 subPars = new ParameterList(); |
|
82 } |
|
83 ParameterElement::~ParameterElement() |
|
84 { |
|
85 delete subPars; |
|
86 } |
|
87 ParameterElement::ParameterElement(const ParameterElement& rhs) |
|
88 { |
|
89 val = rhs.val; |
|
90 if( rhs.subPars ) |
|
91 subPars = new ParameterList(*(rhs.subPars)); |
|
92 else |
|
93 subPars = 0; |
|
94 } |
|
95 |
|
96 void ParameterElement::Clear() |
|
97 { |
|
98 val.clear(); |
|
99 if( subPars ) |
|
100 subPars->clear(); |
|
101 } |
|
102 |
|
103 const ParameterElement& ParameterElement::operator= (const ParameterElement& rhs) |
|
104 { |
|
105 if( this != &rhs ) |
|
106 { |
|
107 val = rhs.val; |
|
108 delete subPars; |
|
109 if( rhs.subPars ) |
|
110 subPars = new ParameterList(*(rhs.subPars)); |
|
111 else |
|
112 subPars = 0; |
|
113 } |
|
114 return *this; |
|
115 } |
|
116 |
|
117 void ParameterElement::SetSubParameters( ParameterList* pPars ) |
|
118 { |
|
119 delete subPars; |
|
120 subPars = pPars; |
|
121 } |
|
122 |
|
123 // ---------------------------------------------------------------------------------------------------------- |
|
124 |
|
125 /** |
|
126 * This structure represents the C++ cv-qualifier. |
|
127 */ |
|
128 struct CVQualifier |
|
129 { |
|
130 static const string constQualifier; |
|
131 static const string volatileQualifier; |
|
132 static const string constVolatileQualifier; |
|
133 |
|
134 CVQualifier() { isConst = false; isVolatile = false; }; |
|
135 |
|
136 /** |
|
137 * Set function gets <code>std::string</code> as an input and sets const |
|
138 * and/or volatile flags according to the input. |
|
139 * @param cvStr String representing the const and / or volatile qualifier |
|
140 * i.e "const", "volatile" or "const volatile". |
|
141 */ |
|
142 void Set(string const& cvStr) |
|
143 { |
|
144 if( cvStr == constQualifier ) |
|
145 { |
|
146 isConst = true; |
|
147 isVolatile = false; |
|
148 } |
|
149 else if( cvStr == volatileQualifier ) |
|
150 { |
|
151 isConst = false; |
|
152 isVolatile = true; |
|
153 } |
|
154 else if( cvStr == constVolatileQualifier ) |
|
155 { |
|
156 isConst = true; |
|
157 isVolatile = true; |
|
158 } |
|
159 else |
|
160 { |
|
161 isConst = false; |
|
162 isVolatile = false; |
|
163 } |
|
164 }; |
|
165 |
|
166 /** |
|
167 * Sets const and/or volatile flags |
|
168 * @param c 'const' qualifier. If TRUE, isConst flag is set to TRUE |
|
169 * @param v 'volatile' qualifier. If TRUE, isVolatile flag is set to TRUE |
|
170 */ |
|
171 void Set( bool c, bool v ) |
|
172 { |
|
173 isConst = c; |
|
174 isVolatile = v; |
|
175 }; |
|
176 bool isConst; |
|
177 bool isVolatile; |
|
178 }; |
|
179 |
|
180 // ---------------------------------------------------------------------------------------------------------- |
|
181 |
|
182 const string CVQualifier::constQualifier = string("const"); |
|
183 const string CVQualifier::volatileQualifier = string("volatile"); |
|
184 const string CVQualifier::constVolatileQualifier = string("const volatile"); |
|
185 |
|
186 // ---------------------------------------------------------------------------------------------------------- |
|
187 |
|
188 |
|
189 // This represents the function signature |
|
190 struct FuncSignature |
|
191 { |
|
192 string nestedName; // name part |
|
193 ParameterList parameters; // parameters |
|
194 CVQualifier cvQualifier; // cv-qualifier of function |
|
195 }; |
|
196 |
|
197 // ---------------------------------------------------------------------------------------------------------- |
|
198 |
|
199 |
|
200 bool AreParametersCompatible(const FunctionParameter& basePar, const FunctionParameter& currPar, unsigned int CompareFlags = COMPARE_FLAG_NONE); |
|
201 string::const_iterator RemoveSpaces(string::const_iterator start, string::const_iterator end, string::const_iterator& result); |
|
202 string::size_type ReadCharsInTag(const string& sig, string& argElem, string::size_type startPos, pair<char, char> const& tag); |
|
203 |
|
204 |
|
205 // ---------------------------------------------------------------------------------------------------------- |
|
206 |
|
207 /** |
|
208 * Reads characters inside the given tag. Starts looking for the |
|
209 * starting tag at startPos. |
|
210 * @param sig Reference to the string representing the function signature. |
|
211 * @param argElem Reference to the string in which the parameter list is read. |
|
212 * @param startPos Tag starting point (e.g. '<' or '(') is startet to be searched |
|
213 * after this location. |
|
214 * @param tag This object defines the starting and ending characters of the tag (e.g. '<' and '>'). |
|
215 * @return Size of the string between the start and end characters of the tag. |
|
216 */ |
|
217 string::size_type ReadCharsInTag(const string& sig, string& argElem, string::size_type startPos, pair<char, char> const& tag) |
|
218 { |
|
219 string::size_type sigLen = sig.size(); |
|
220 if( sigLen == 0 ) |
|
221 { |
|
222 return 0; |
|
223 } |
|
224 |
|
225 if( sig.at(startPos) != tag.first ) |
|
226 { |
|
227 startPos = sig.find(tag.first, startPos); |
|
228 } |
|
229 |
|
230 string::size_type currPos = startPos; |
|
231 |
|
232 int tagCnt = 0; // This counter will be increased by 1 when tag starts and |
|
233 // decreased by 1 when the tag ends. When it reaches zero, |
|
234 // the tag has been completely read. |
|
235 |
|
236 do |
|
237 { |
|
238 const char current = sig.at(currPos); // throws, if out of bounds |
|
239 |
|
240 if( current == tag.first ) |
|
241 ++tagCnt; // tag starts |
|
242 else if( current == tag.second ) |
|
243 --tagCnt; // tag ends |
|
244 |
|
245 ++currPos; |
|
246 } |
|
247 while( tagCnt > 0 ); |
|
248 |
|
249 string::size_type len = currPos - startPos; |
|
250 |
|
251 if( len > 0 ) |
|
252 { |
|
253 argElem.append(sig.begin() + startPos, sig.begin() + currPos); |
|
254 } |
|
255 |
|
256 return len; |
|
257 } |
|
258 |
|
259 // ---------------------------------------------------------------------------------------------------------- |
|
260 |
|
261 /** |
|
262 * ParseParameterList. Recursively parses the given sub-string into |
|
263 * parameter list object. |
|
264 * @param funcSignature Reference to the string representing the function |
|
265 * signature. |
|
266 * @param parBegin Location where the parameter list starts. |
|
267 * @param parEnd Location where the parameter list ends. |
|
268 * @param parameters Parameter list of the function is returned here. |
|
269 */ |
|
270 void ParseParameterList(const string& funcSignature, |
|
271 string::size_type parBegin, |
|
272 string::size_type parEnd, |
|
273 ParameterList& parameters) |
|
274 { |
|
275 FunctionParameter par; // Object holding the current function parameter during parsing |
|
276 ParameterElement parElem; // Object holding the current parameter element during parsing |
|
277 |
|
278 while( parBegin <= parEnd ) |
|
279 { |
|
280 char current = funcSignature.at(parBegin); |
|
281 switch( current ) |
|
282 { |
|
283 case '(': |
|
284 case '<': |
|
285 { |
|
286 // Character '(' or '<' means that a new parameter list starts. |
|
287 // New parameter list is stored as a sub-parameter list of the current parameter. |
|
288 |
|
289 if( parElem.Value().size() > 0 ) |
|
290 { |
|
291 par.push_back(parElem); // Put the previous element in the list... |
|
292 parElem.Clear(); // ...and clear the object for the next round. |
|
293 } |
|
294 |
|
295 string parListStr; // String representing the sub-parameterlist is stored here. |
|
296 int len = 0; |
|
297 char tagEndMark = current == '(' ? ')' : '>'; |
|
298 |
|
299 // Put the value "<>" or "()" |
|
300 parElem.Value().push_back(current); |
|
301 parElem.Value().push_back(tagEndMark); |
|
302 |
|
303 // Read sub-parameters... |
|
304 len += ReadCharsInTag( funcSignature, parListStr, parBegin, make_pair(current, tagEndMark) ); |
|
305 // ...and parse them |
|
306 if( parElem.SubParameters() == 0 ) |
|
307 { |
|
308 parElem.SetSubParameters(new ParameterList()); |
|
309 } |
|
310 ParseParameterList(parListStr, 1, len-1, *(parElem.SubParameters()) ); |
|
311 |
|
312 par.push_back(parElem); // Put the current element in the list... |
|
313 parElem.Clear(); // ...and clear the object for the next round. |
|
314 |
|
315 parBegin += len; |
|
316 break; |
|
317 } |
|
318 case ' ': |
|
319 { |
|
320 // space means that we have a new parameter element |
|
321 |
|
322 if( parElem.Value().size() > 0 ) |
|
323 { |
|
324 par.push_back(parElem); // Put the previous element in the list... |
|
325 parElem.Clear(); // ...and clear the object for the next round. |
|
326 } |
|
327 ++parBegin; |
|
328 break; |
|
329 } |
|
330 case ',': |
|
331 case ')': |
|
332 case '>': |
|
333 { |
|
334 // Parameter completed |
|
335 |
|
336 if( parElem.Value().size() > 0 ) |
|
337 { |
|
338 par.push_back(parElem); // Put the previous element in the list... |
|
339 parElem.Clear(); //...and clear the object for the next round. |
|
340 } |
|
341 if( par.size() > 0 ) |
|
342 { |
|
343 // Put the parameter in the parameter list... |
|
344 parameters.push_back(par); |
|
345 par.clear(); //...and clear the object for the next round. |
|
346 } |
|
347 ++parBegin; |
|
348 break; |
|
349 } |
|
350 case '*': |
|
351 case '&': |
|
352 { |
|
353 // We have a pointer or reference |
|
354 |
|
355 if( parElem.Value().size() > 0 ) |
|
356 { |
|
357 par.push_back(parElem); // Put the previous element in the list... |
|
358 parElem.Clear(); //...and clear the object for the next round. |
|
359 } |
|
360 // Put also the current element in the list (i.e. '*' or '&') |
|
361 parElem.Value().push_back(current); |
|
362 par.push_back(parElem); |
|
363 parElem.Clear(); // ...and clear the object for the next round. |
|
364 ++parBegin; |
|
365 break; |
|
366 } |
|
367 default: |
|
368 { |
|
369 parElem.Value().push_back(funcSignature.at(parBegin)); |
|
370 ++parBegin; |
|
371 break; |
|
372 } |
|
373 } |
|
374 } |
|
375 } |
|
376 |
|
377 // ---------------------------------------------------------------------------------------------------------- |
|
378 |
|
379 /** |
|
380 * ParseFunctionSignature |
|
381 * Parses function signature and returns following elements for the given |
|
382 * function: |
|
383 * - name |
|
384 * - parameters, which are splitted in elements and sub-parameters |
|
385 * - cv-qualifier of the function |
|
386 * |
|
387 * Parameters are splitted in following elements: |
|
388 * - type (int, char, etc...) |
|
389 * - cv-qualifier (const and/or volatile) |
|
390 * - pointer symbol (*) |
|
391 * - reference symbol (&) |
|
392 * - parameter list "()" |
|
393 * - template parameter list "<>". |
|
394 * |
|
395 * Parameter lists are further splitted into sub-parameters, which also consists |
|
396 * of elements and sub-parameters. |
|
397 * |
|
398 * @param funcSignature Reference to the string representing the function signature |
|
399 * @param signature Parsed function signature is returned in this object. |
|
400 */ |
|
401 void ParseFunctionSignature( const string& funcSignature, FuncSignature& signature ) |
|
402 { |
|
403 // Find the start and end positions of the "main" parameter list |
|
404 string::size_type openBracketIndex = funcSignature.find_first_of('('); |
|
405 string::size_type closeBracketIndex = funcSignature.find_last_of(')'); |
|
406 |
|
407 if(openBracketIndex == string::npos || closeBracketIndex == string::npos ) |
|
408 { |
|
409 // No parameter list, so the given string may be for example |
|
410 // "virtual table for MyClass" |
|
411 signature.nestedName = funcSignature; |
|
412 return; |
|
413 } |
|
414 |
|
415 // Remove preceding and trailing spaces from the function name part: |
|
416 string::const_iterator start; |
|
417 string::const_iterator end = RemoveSpaces( |
|
418 funcSignature.begin(), |
|
419 funcSignature.begin() + openBracketIndex, |
|
420 start); |
|
421 |
|
422 // Store the name part |
|
423 signature.nestedName.append(start, end); |
|
424 |
|
425 // Parse function parameters |
|
426 ParseParameterList(funcSignature, openBracketIndex+1, closeBracketIndex, signature.parameters); |
|
427 |
|
428 // Parse the cv-qualifier of the function. |
|
429 end = RemoveSpaces(funcSignature.begin() + (closeBracketIndex+1), funcSignature.end(), start); |
|
430 signature.cvQualifier.Set(string(start,end)); |
|
431 } |
|
432 |
|
433 // ---------------------------------------------------------------------------------------------------------- |
|
434 |
|
435 /** |
|
436 * AreFunctionsCompatible |
|
437 * Compares two function signatures for backward binary compatibility. |
|
438 * This function first parses the function signature into name, parameter list |
|
439 * and cv-qualifier parts. The name part is compared first and if names don't |
|
440 * match, return false. Then parameter lists are given to |
|
441 * <code>AreParametersCompatible</code> function for comparison. |
|
442 * And finally cv-qualifiers of functions are compared. This comparison function |
|
443 * allows changing non-const function to const (i.e adding 'const'-qualifier for |
|
444 * a function). |
|
445 * @param baselineFunc Reference to the string representing the baseline |
|
446 * platform's function signature |
|
447 * @param currentFunc Reference to the string representing the current |
|
448 * platform's function signature |
|
449 * @return bool value indicating whether the two functions are backward binary |
|
450 * compatible. |
|
451 */ |
|
452 TypeOfSeverity AreFunctionsCompatible(const string& baselineFunc, const string& currentFunc) |
|
453 { |
|
454 TypeOfSeverity retSeverity = NO_BREAK; |
|
455 // First split functions into name part, parameter list and possible cv-qualifier: |
|
456 FuncSignature baseFunc; |
|
457 FuncSignature currFunc; |
|
458 |
|
459 ParseFunctionSignature(baselineFunc, baseFunc); |
|
460 ParseFunctionSignature(currentFunc, currFunc); |
|
461 |
|
462 // Check the const qualifier of the function: |
|
463 if( baseFunc.cvQualifier.isConst != currFunc.cvQualifier.isConst ) |
|
464 { |
|
465 // const qualifier of the function has been removed(either const to non-const or vise versa) |
|
466 // Results in SC break |
|
467 retSeverity = CONFIRMED_SC_BREAK; |
|
468 goto EXIT_POINT; |
|
469 } |
|
470 |
|
471 // Check the number of parameters: |
|
472 if( baseFunc.parameters.size() != currFunc.parameters.size() ) |
|
473 { |
|
474 retSeverity = POSSIBLE_BC_CONFIRMED_SC_BREAK; // Number of parameters does not match. |
|
475 goto EXIT_POINT; |
|
476 } |
|
477 |
|
478 // Then check name part: |
|
479 if( baseFunc.nestedName.compare(currFunc.nestedName) != 0 ) |
|
480 { |
|
481 retSeverity = POSSIBLE_BC_SC_BREAK; // Names do not match. |
|
482 goto EXIT_POINT; |
|
483 } |
|
484 |
|
485 // Check the parameters: |
|
486 for( unsigned int i = 0; i < baseFunc.parameters.size(); ++i ) |
|
487 { |
|
488 if( AreParametersCompatible(baseFunc.parameters[i], currFunc.parameters[i], COMPARE_FLAG_ALLOW_CONST_ADDITION) == false ) |
|
489 { |
|
490 retSeverity = POSSIBLE_BC_SC_BREAK; // Parameters do not match |
|
491 goto EXIT_POINT; |
|
492 } |
|
493 } |
|
494 |
|
495 if( baseFunc.cvQualifier.isVolatile != currFunc.cvQualifier.isVolatile ) |
|
496 { |
|
497 retSeverity = POSSIBLE_SC_BREAK; // volatile qualifier does not match |
|
498 goto EXIT_POINT; |
|
499 } |
|
500 |
|
501 EXIT_POINT: |
|
502 return retSeverity; |
|
503 } |
|
504 |
|
505 // ---------------------------------------------------------------------------------------------------------- |
|
506 |
|
507 /** |
|
508 * Removes preceding and trailing spaces from the given string |
|
509 * @return iterator to the end of the trimmed string. |
|
510 * @param start Iterator to the beginning of the string. |
|
511 * @param end Iterator to the end of the string. |
|
512 * @param result Iterator to the beginning of the trimmed string. |
|
513 */ |
|
514 string::const_iterator RemoveSpaces(string::const_iterator start, |
|
515 string::const_iterator end, |
|
516 string::const_iterator& result) |
|
517 { |
|
518 // Remove spaces from the beginning of the string: |
|
519 result = start; |
|
520 while( result != end && *result == ' ' ) |
|
521 { |
|
522 ++result; |
|
523 } |
|
524 |
|
525 // Remove spaces from the end of the string: |
|
526 string::const_iterator resultEnd = end; |
|
527 while( resultEnd != result && *(resultEnd-1) == ' ' ) |
|
528 { |
|
529 --resultEnd; |
|
530 } |
|
531 |
|
532 return resultEnd; |
|
533 } |
|
534 |
|
535 // ---------------------------------------------------------------------------------------------------------- |
|
536 |
|
537 /** |
|
538 * AreParametersCompatible |
|
539 * Checks if two function parameters are compatible with each other. |
|
540 * Allows added const qualifiers in current version of the parameter. |
|
541 * This function loops through parameters, that are splitted into elements, |
|
542 * and compares them element by element. If the elements differ and current |
|
543 * platform's element is 'const' qualifier, skip the current platform's element |
|
544 * and compare next one to the baseline platform's element. |
|
545 * @param basePar Reference to the parameter object representing the parameter |
|
546 * of the baseline platform's function. |
|
547 * @param currPar Reference to the parameter object representing the parameter |
|
548 * of the current platform's function. |
|
549 * @param compareFlags Comparison policy flags that should be used when comparing |
|
550 * the parameters. |
|
551 * @return bool value indicating if the two parameters are backward binary compatible. |
|
552 */ |
|
553 bool AreParametersCompatible( const FunctionParameter& basePar, |
|
554 const FunctionParameter& currPar, |
|
555 unsigned int compareFlags ) |
|
556 { |
|
557 const string cvQualConst("const"); // const qualifier |
|
558 const string ptrStr("*"); // pointer |
|
559 const string refStr("&"); // reference |
|
560 if( basePar.size() > currPar.size() ) |
|
561 { |
|
562 // Something has been removed: |
|
563 return false; |
|
564 } |
|
565 |
|
566 vector<ParameterElement>::const_iterator baseElem = basePar.begin(); |
|
567 vector<ParameterElement>::const_iterator currElem = currPar.begin(); |
|
568 while( baseElem != basePar.end() && currElem != currPar.end() ) |
|
569 { |
|
570 if( baseElem->Value().compare(currElem->Value()) != 0 ) |
|
571 { |
|
572 if( currElem->Value().compare(cvQualConst) == 0 && |
|
573 (compareFlags & COMPARE_FLAG_ALLOW_CONST_ADDITION) ) |
|
574 { |
|
575 // COMPARE_FLAG_ALLOW_CONST_ADDITION used, so it is acceptable that |
|
576 // 'const' qualifier has been added to current parameter. So let's skip |
|
577 // the 'const' qualifier -element and compare next elements to see if |
|
578 // the parameter is otherwise compatible. |
|
579 ++currElem; |
|
580 continue; |
|
581 } |
|
582 else |
|
583 { |
|
584 return false; // Parameter elements do not match |
|
585 } |
|
586 } |
|
587 |
|
588 // Now, lets take the sub-parameters (if any) and call this function recursively: |
|
589 const ParameterList* baseSubPars = baseElem->SubParameters(); |
|
590 const ParameterList* currSubPars = currElem->SubParameters(); |
|
591 if( baseSubPars != 0 && currSubPars != 0 ) |
|
592 { |
|
593 if( baseSubPars->size() != currSubPars->size() ) |
|
594 return false; // Number of sub-parameters do not match |
|
595 |
|
596 for( unsigned int subI = 0; subI < baseSubPars->size(); ++subI ) |
|
597 { |
|
598 // Here we are dealing with sub-parameters (e.g parameter list of a function |
|
599 // pointer parameter), and no 'const addition' flags etc. are used anymore. |
|
600 if( AreParametersCompatible( baseSubPars->at(subI), currSubPars->at(subI)) == false ) |
|
601 return false; |
|
602 } |
|
603 } |
|
604 else if( baseSubPars != currSubPars ) |
|
605 { |
|
606 return false; // Other one's subparameter list pointer is NULL |
|
607 } |
|
608 |
|
609 ++baseElem; |
|
610 ++currElem; |
|
611 } |
|
612 |
|
613 // Check for the const pointer. Actually in this case the 'const' qualifier |
|
614 // is left out from the mangled/demangled signature, but just to be sure... |
|
615 // For example: 'int *' changed to 'int * const' |
|
616 if( baseElem == basePar.end() && currElem != currPar.end() ) |
|
617 { |
|
618 if( currElem->Value().compare(cvQualConst) == 0 ) |
|
619 { |
|
620 ++currElem; |
|
621 } |
|
622 } |
|
623 |
|
624 // Check that all the elements have been "consumed": |
|
625 return baseElem == basePar.end() && currElem == currPar.end(); |
|
626 } |
|
627 |
|
628 // ---------------------------------------------------------------------------------------------------------- |