ChimeraTK-DeviceAccess 03.26.00
Loading...
Searching...
No Matches
FixedPointConverter.h
Go to the documentation of this file.
1// SPDX-FileCopyrightText: Deutsches Elektronen-Synchrotron DESY, MSK, ChimeraTK Project <chimeratk-support@desy.de>
2// SPDX-License-Identifier: LGPL-3.0-or-later
3#pragma once
4
5#include "Exception.h"
6#include "NumericConverter.h"
8
9#include <boost/fusion/algorithm.hpp>
10#include <boost/fusion/container.hpp>
11#include <boost/fusion/sequence.hpp>
12#include <boost/numeric/conversion/cast.hpp>
13
14#include <bit>
15#include <cassert>
16#include <cmath>
17#include <cstdint>
18#include <iostream>
19#include <limits>
20#include <sstream>
21#include <string>
22#include <type_traits>
23
24namespace ChimeraTK {
25
30 template<typename RawType>
32 public:
47 std::string variableName, unsigned int nBits = 32, int fractionalBits = 0, bool isSignedFlag = true);
48
56 template<typename UserType>
57 RawType toRaw(UserType cookedValue) const;
58
63 template<typename UserType, typename RAW_ITERATOR, typename COOKED_ITERATOR>
65 const RAW_ITERATOR& raw_begin, const RAW_ITERATOR& raw_end, const COOKED_ITERATOR& cooked_begin) const {
66 static_assert(std::is_same<typename std::iterator_traits<RAW_ITERATOR>::iterator_category,
67 std::random_access_iterator_tag>::value,
68 "RAW_ITERATOR template argument must be a random access iterator.");
69 static_assert(std::is_same<typename std::iterator_traits<COOKED_ITERATOR>::iterator_category,
70 std::random_access_iterator_tag>::value,
71 "COOKED_ITERATOR template argument must be a random access iterator.");
72 static_assert(std::is_same<typename std::iterator_traits<RAW_ITERATOR>::value_type, int8_t>::value ||
73 std::is_same<typename std::iterator_traits<RAW_ITERATOR>::value_type, int16_t>::value ||
74 std::is_same<typename std::iterator_traits<RAW_ITERATOR>::value_type, int32_t>::value ||
75 std::is_same<typename std::iterator_traits<RAW_ITERATOR>::value_type, int64_t>::value,
76 "RAW_ITERATOR template argument must be an iterator with value type equal to int8_t, int16_t, int32_t or "
77 "int64_t");
78 static_assert(std::is_same<typename std::iterator_traits<COOKED_ITERATOR>::value_type, UserType>::value,
79 "COOKED_ITERATOR template argument must be an iterator with value type equal to the UserType template "
80 "argument.");
81 vectorToCooked_impl<UserType, RAW_ITERATOR, COOKED_ITERATOR>::impl(*this, raw_begin, raw_end, cooked_begin);
82 }
83 template<typename UserType, typename RAW_ITERATOR, typename COOKED_ITERATOR>
85 static void impl(const FixedPointConverter& fpc, const RAW_ITERATOR& raw_begin, const RAW_ITERATOR& raw_end,
86 COOKED_ITERATOR cooked_begin);
87 };
88
90 template<typename UserType>
91 UserType scalarToCooked(RawType const& raw) const {
92 UserType cooked;
93 vectorToCooked<UserType>(&raw, (&raw) + 1, &cooked);
94 return cooked;
95 }
96
98 [[nodiscard]] unsigned int getNBits() const { return _nBits; }
99
101 [[nodiscard]] int getFractionalBits() const { return _fractionalBits; }
102
104 [[nodiscard]] bool isSigned() const { return _isSigned; }
105
108 bool operator==(const FixedPointConverter& other) const {
109 return _nBits == other._nBits && _fractionalBits == other._fractionalBits && _isSigned == other._isSigned;
110 }
111 bool operator!=(const FixedPointConverter& other) const { return !operator==(other); }
112
113 private:
114 std::string _variableName;
115 unsigned int _nBits;
116 int _fractionalBits;
117 bool _isSigned;
118
121 double _fractionalBitsCoefficient;
122
126 double _inverseFractionalBitsCoefficient;
127
128 RawType _signBitMask{};
129 RawType _usedBitsMask{};
130 RawType _unusedBitsMask{};
131
132 RawType _maxRawValue{};
133 RawType _minRawValue{};
134
136 userTypeMap _maxCookedValues;
137
139 userTypeMap _minCookedValues;
140
143 FixedUserTypeMap<int> conversionBranch_toCooked;
144
147 const static int zero;
148
150 class initCoefficients {
151 public:
152 explicit initCoefficients(FixedPointConverter* fpc) : _fpc(fpc) {}
153
154 template<typename Pair>
155 void operator()(Pair) const {
156 // obtain UserType from given fusion::pair type
157 using UserType = typename Pair::first_type;
158
159 // compute conversion branches. Needs to be done before the subsequent calls to toCooked()!
160 if(_fpc->_nBits == 16 && _fpc->_fractionalBits == 0 && !_fpc->_isSigned) {
161 boost::fusion::at_key<UserType>(_fpc->conversionBranch_toCooked.table) = 9;
162 }
163 else if(_fpc->_nBits == 16 && _fpc->_fractionalBits == 0 && _fpc->_isSigned) {
164 boost::fusion::at_key<UserType>(_fpc->conversionBranch_toCooked.table) = 10;
165 }
166 else if(std::numeric_limits<UserType>::is_integer && _fpc->_fractionalBits == 0 && !_fpc->_isSigned) {
167 boost::fusion::at_key<UserType>(_fpc->conversionBranch_toCooked.table) = 1;
168 }
169 else if(std::numeric_limits<UserType>::is_integer && _fpc->_fractionalBits == 0 && _fpc->_isSigned) {
170 boost::fusion::at_key<UserType>(_fpc->conversionBranch_toCooked.table) = 2;
171 }
172 else if(_fpc->_nBits == 16 && _fpc->_fractionalBits < 0 && _fpc->_fractionalBits > -16 && !_fpc->_isSigned) {
173 boost::fusion::at_key<UserType>(_fpc->conversionBranch_toCooked.table) = 7;
174 }
175 else if(_fpc->_nBits == 16 && _fpc->_fractionalBits < 0 && _fpc->_fractionalBits > -16 && _fpc->_isSigned) {
176 boost::fusion::at_key<UserType>(_fpc->conversionBranch_toCooked.table) = 8;
177 }
178 else if(_fpc->_nBits == 16 && !_fpc->_isSigned) {
179 boost::fusion::at_key<UserType>(_fpc->conversionBranch_toCooked.table) = 5;
180 }
181 else if(_fpc->_nBits == 16 && _fpc->_isSigned) {
182 boost::fusion::at_key<UserType>(_fpc->conversionBranch_toCooked.table) = 6;
183 }
184 else if(!_fpc->_isSigned) {
185 boost::fusion::at_key<UserType>(_fpc->conversionBranch_toCooked.table) = 3;
186 }
187 else if(_fpc->_isSigned) {
188 boost::fusion::at_key<UserType>(_fpc->conversionBranch_toCooked.table) = 4;
189 }
190
191 // compute minimum and maximum values in cooked representation
192 boost::fusion::at_key<UserType>(_fpc->_minCookedValues) = _fpc->scalarToCooked<UserType>(_fpc->_minRawValue);
193 boost::fusion::at_key<UserType>(_fpc->_maxCookedValues) = _fpc->scalarToCooked<UserType>(_fpc->_maxRawValue);
194 }
195
196 private:
198 };
199
202 template<typename UserType, typename std::enable_if<std::is_signed<UserType>{}, int>::type = 0>
203 bool isNegativeUserType(UserType value) const;
204 template<typename UserType, typename std::enable_if<!std::is_signed<UserType>{}, int>::type = 0>
205 bool isNegativeUserType(UserType value) const;
206
207 // helper function: force unused leading bits to 0 for positive or 1 for negative numbers
208 // NOLINTBEGIN(hicpp-signed-bitwise)
209 // NOLINTBEGIN(bugprone-narrowing-conversions)
210 // Turn off the linter warning. Yes, we are fiddling with the bit interpretation here, that's the whole point.
211 void padUnusedBits(RawType& rawValue) const {
212 if(!(rawValue & _signBitMask)) {
213 rawValue &= _usedBitsMask;
214 }
215 else {
216 rawValue |= _unusedBitsMask;
217 }
218 }
219 // NOLINTEND(bugprone-narrowing-conversions)
220 // NOLINTEND(hicpp-signed-bitwise)
221 };
222
223 /********************************************************************************************************************/
224 template<typename RawType>
225 template<typename UserType, typename RAW_ITERATOR, typename COOKED_ITERATOR>
227 const FixedPointConverter& fpc, const RAW_ITERATOR& raw_begin, const RAW_ITERATOR& raw_end,
228 COOKED_ITERATOR cooked_begin) {
229 // Handle integer and floating-point types differently.
230 switch(boost::fusion::at_key<UserType>(fpc.conversionBranch_toCooked.table)) {
231 case 1: { // std::numeric_limits<UserType>::is_integer && _fpc->_fractionalBits == 0 && !_fpc->_isSigned
232 std::transform(raw_begin, raw_end, cooked_begin, [&fpc](RawType rawValue) {
233 fpc.padUnusedBits(rawValue);
234 if constexpr(sizeof(RawType) == 4) {
235 return numericToUserType<UserType>(*(std::bit_cast<uint32_t*>(&rawValue)));
236 }
237 else if constexpr(sizeof(RawType) == 8) {
238 return numericToUserType<UserType>(*(std::bit_cast<uint64_t*>(&rawValue)));
239 }
240 });
241 break;
242 }
243 case 2: { // std::numeric_limits<UserType>::is_integer && _fpc->_fractionalBits == 0 && _fpc->_isSigned
244 std::transform(raw_begin, raw_end, cooked_begin, [&fpc](RawType rawValue) {
245 fpc.padUnusedBits(rawValue);
246 return numericToUserType<UserType>(rawValue);
247 });
248 break;
249 }
250 case 9: { // _fpc->_nBits == 16 && _fpc->_fractionalBits == 0 && !_fpc->_isSigned
251 std::transform(raw_begin, raw_end, cooked_begin, [](const auto& rawValue) {
252 return numericToUserType<UserType>(*(std::bit_cast<const uint16_t*>(&rawValue)));
253 });
254 break;
255 }
256 case 10: { // _fpc->_nBits == 16 && _fpc->_fractionalBits == 0 && _fpc->_isSigned
257 std::transform(raw_begin, raw_end, cooked_begin, [](const auto& rawValue) {
258 return numericToUserType<UserType>(*(std::bit_cast<const int16_t*>(&rawValue)));
259 });
260 break;
261 }
262 case 7: { // _fpc->_nBits == 16 && _fpc->_fractionalBits < 0 && _fpc->_fractionalBits > -16 && !_fpc->_isSigned
263 const auto f = static_cast<uint32_t>(fpc._fractionalBitsCoefficient);
264 std::transform(raw_begin, raw_end, cooked_begin, [f](const auto& rawValue) {
265 return numericToUserType<UserType>(f * *(std::bit_cast<const uint16_t*>(&rawValue)));
266 });
267 break;
268 }
269 case 8: { // _fpc->_nBits == 16 && _fpc->_fractionalBits < 0 && _fpc->_fractionalBits > -16 && _fpc->_isSigned
270 const auto f = static_cast<int32_t>(fpc._fractionalBitsCoefficient);
271 std::transform(raw_begin, raw_end, cooked_begin, [f](const auto& rawValue) {
272 return numericToUserType<UserType>(f * *(std::bit_cast<const int16_t*>(&rawValue)));
273 });
274 break;
275 }
276 case 5: { // _fpc->_nBits == 16 && !_fpc->_isSigned
277 const auto f = fpc._fractionalBitsCoefficient;
278 std::transform(raw_begin, raw_end, cooked_begin, [f](const auto& rawValue) {
279 return numericToUserType<UserType>(f * *(std::bit_cast<const uint16_t*>(&rawValue)));
280 });
281 break;
282 }
283 case 6: { // _fpc->_nBits == 16 && _fpc->_isSigned
284 const auto f = fpc._fractionalBitsCoefficient;
285 std::transform(raw_begin, raw_end, cooked_begin, [f](const auto& rawValue) {
286 return numericToUserType<UserType>(f * *(std::bit_cast<const int16_t*>(&rawValue)));
287 });
288 break;
289 }
290 case 3: { // !_fpc->_isSigned
291 const auto f = fpc._fractionalBitsCoefficient;
292 std::transform(raw_begin, raw_end, cooked_begin, [&fpc, f](RawType rawValue) {
293 fpc.padUnusedBits(rawValue);
294 if constexpr(sizeof(RawType) == 4) {
295 return numericToUserType<UserType>(f * *(std::bit_cast<uint32_t*>(&rawValue)));
296 }
297 else if constexpr(sizeof(RawType) == 8) {
298 return numericToUserType<UserType>(f * *(std::bit_cast<uint64_t*>(&rawValue)));
299 }
300 });
301 break;
302 }
303 case 4: { // _fpc->_isSigned
304 const auto f = fpc._fractionalBitsCoefficient;
305 std::transform(raw_begin, raw_end, cooked_begin, [&fpc, f](RawType rawValue) {
306 fpc.padUnusedBits(rawValue);
307 auto ttt = numericToUserType<UserType>(f * rawValue);
308 return ttt;
309 });
310 break;
311 }
312 default: {
313 std::cerr << "Fixed point converter configuration is corrupt." << std::endl;
314 std::terminate();
315 }
316 }
317 }
318 // NOLINTEND(cppcoreguidelines-pro-type-reinterpret-cast)
319
320 /********************************************************************************************************************/
321 template<typename RawType>
322 template<typename UserType>
323 RawType FixedPointConverter<RawType>::toRaw(UserType cookedValue) const {
324 if constexpr(std::is_same<UserType, std::string>::value) {
325 if(_fractionalBits == 0) { // use integer conversion
326 if(_isSigned) {
327 return toRaw(std::stoi(cookedValue));
328 }
329 return toRaw(std::stoul(cookedValue));
330 }
331
332 return toRaw(std::stod(cookedValue));
333 }
334 else if constexpr(std::is_same<UserType, Boolean>::value) {
335 if((bool)cookedValue) { // use integer conversion
336 return 1;
337 }
338 return 0;
339 }
340 else if constexpr(std::is_same<UserType, Void>::value) {
341 return 0;
342 }
343 else {
344 // Do a range check first. The later overflow check in the conversion is not
345 // sufficient, since we can have non-standard word sizes like 12 bits.
346 if(cookedValue < boost::fusion::at_key<UserType>(_minCookedValues)) {
347 return _minRawValue;
348 }
349 if(cookedValue > boost::fusion::at_key<UserType>(_maxCookedValues)) {
350 return _maxRawValue;
351 }
352
353 // handle integer and floating-point types differently
354 if constexpr(std::numeric_limits<UserType>::is_integer) {
355 if(_fractionalBits == 0) {
356 // extract the sign and leave the positive number
357 bool isNegative = isNegativeUserType(cookedValue);
358 if(isNegative && !_isSigned) {
359 return _minRawValue;
360 }
361 if(isNegative) {
362 cookedValue = -(cookedValue + 1); // bit inversion, ~ operator cannot be used as UserType might be double
363 }
364 // cast into raw type
365 auto rawValue = static_cast<RawType>(cookedValue);
366
367 // handle sign
368 if(_isSigned && isNegative) {
369 rawValue = ~rawValue;
370 }
371
372 // return with bit mask applied
373 return rawValue & _usedBitsMask;
374 }
375 }
376 // convert into double and scale by fractional bit coefficient
377 double d_cooked = _inverseFractionalBitsCoefficient * static_cast<double>(cookedValue);
378
379 // Convert into either signed or unsigned int32_t or int64t, depending on _isSigned,
380 // so the conversion handles the sign correctly and size of Raw type.
381 // Store always in RawType. The conversion will properly round, when
382 // needed. Negative and positive overflow exceptions need to be caught for some corner
383 // cases (e.g. number of fractional bits >= number of bits in total).
384 RawType raw;
385 if(_isSigned) {
386 if constexpr(sizeof(RawType) == 4) {
387 raw = RawType(numeric::convert<int32_t>(d_cooked));
388 }
389 else if constexpr(sizeof(RawType) == 8) {
390 raw = RawType(numeric::convert<int64_t>(d_cooked));
391 }
392 }
393 else {
394 if constexpr(sizeof(RawType) == 4) {
395 raw = RawType(numeric::convert<uint32_t>(d_cooked));
396 }
397 else if constexpr(sizeof(RawType) == 8) {
398 raw = RawType(numeric::convert<uint64_t>(d_cooked));
399 }
400 }
401
402 // apply bit mask
403 // NOLINTNEXTLINE(hicpp-signed-bitwise)
404 return raw & _usedBitsMask;
405 }
406 }
407
408 /********************************************************************************************************************/
409 template<typename RawType>
410 template<typename UserType, typename std::enable_if<std::is_signed<UserType>{}, int>::type>
411 bool FixedPointConverter<RawType>::isNegativeUserType(UserType value) const {
412 return static_cast<bool>(value < 0);
413 }
414
415 template<typename RawType>
416 template<typename UserType, typename std::enable_if<!std::is_signed<UserType>{}, int>::type>
417 bool FixedPointConverter<RawType>::isNegativeUserType(UserType /*value*/) const {
418 return false;
419 }
420
421 /********************************************************************************************************************/
422 /* specialization for a std::string as UserType*/
423 template<typename RawType>
424 template<typename RAW_ITERATOR, typename COOKED_ITERATOR>
425 struct FixedPointConverter<RawType>::vectorToCooked_impl<std::string, RAW_ITERATOR, COOKED_ITERATOR> {
426 static void impl(const FixedPointConverter& fpc, const RAW_ITERATOR& raw_begin, const RAW_ITERATOR& raw_end,
427 COOKED_ITERATOR cooked_begin) {
428 if(fpc._fractionalBits == 0) { // use integer conversion
429 if(fpc._isSigned) {
430 std::vector<int32_t> intValues(raw_end - raw_begin);
431 fpc.vectorToCooked<int32_t>(raw_begin, raw_end, intValues.begin());
432 for(auto it : intValues) {
433 *cooked_begin = std::to_string(it);
434 ++cooked_begin;
435 }
436 }
437 else {
438 std::vector<uint32_t> uintValues(raw_end - raw_begin);
439 fpc.vectorToCooked<uint32_t>(raw_begin, raw_end, uintValues.begin());
440 for(auto it : uintValues) {
441 *cooked_begin = std::to_string(it);
442 ++cooked_begin;
443 }
444 }
445 }
446 else {
447 std::vector<double> doubleValues(raw_end - raw_begin);
448 fpc.vectorToCooked<double>(raw_begin, raw_end, doubleValues.begin());
449 for(auto it : doubleValues) {
450 *cooked_begin = std::to_string(it);
451 ++cooked_begin;
452 }
453 }
454 }
455 };
456
457 /********************************************************************************************************************/
458 template<typename RawType>
460 std::string variableName, unsigned int nBits, int fractionalBits, bool isSignedFlag)
461 : _variableName(std::move(variableName)), _nBits(nBits), _fractionalBits(fractionalBits), _isSigned(isSignedFlag),
462 _fractionalBitsCoefficient(pow(2., -fractionalBits)), _inverseFractionalBitsCoefficient(pow(2., fractionalBits)) {
463 _fractionalBitsCoefficient = pow(2., -_fractionalBits);
464 _inverseFractionalBitsCoefficient = pow(2., _fractionalBits);
465
466 const auto maxBits = (sizeof(RawType) * 8);
467 if(nBits > maxBits) {
468 std::stringstream errorMessage;
469 errorMessage << "The number of bits must be <= " << maxBits << ", but is " << nBits;
470 throw ChimeraTK::logic_error(errorMessage.str());
471 }
472
473 // For floating-point types: check if number of fractional bits are complying
474 // with the dynamic range Note: positive fractional bits give us smaller
475 // numbers and thus correspond to negative exponents!
476 if((fractionalBits > -std::numeric_limits<double>::min_exponent - static_cast<int>(nBits)) ||
477 (fractionalBits < -std::numeric_limits<double>::max_exponent + static_cast<int>(nBits))) {
478 std::stringstream errorMessage;
479 errorMessage << "The number of fractional bits exceeds the dynamic"
480 << " range of a double.";
481 throw ChimeraTK::logic_error(errorMessage.str());
482 }
483
484 // compute mask for the signed bit
485 // keep the mask at 0 if unsigned to simplify further calculations
486 _signBitMask = 0;
487 if(_isSigned && nBits > 0) {
488 // NOLINTNEXTLINE(hicpp-signed-bitwise)
489 _signBitMask = RawType(1) << (nBits - 1); // the highest valid bit is the sign
490 }
491
492 // compute masks of used and unused bits
493 // NOLINTNEXTLINE(hicpp-signed-bitwise)
494 _usedBitsMask = ~RawType(0);
495 if(nBits < sizeof(RawType) * 8) {
496 _usedBitsMask = static_cast<RawType>((RawType(1) << nBits) - RawType(1));
497 }
498 // NOLINTNEXTLINE(hicpp-signed-bitwise)
499 _unusedBitsMask = ~_usedBitsMask;
500
501 // compute minimum and maximum value in raw representation
502 // NOLINTNEXTLINE(hicpp-signed-bitwise)
503 _maxRawValue = _usedBitsMask ^ _signBitMask; // bitwise xor: first bit is 0 if signed
504 // NOLINTNEXTLINE(hicpp-signed-bitwise)
505 _minRawValue = _signBitMask; // if only the sign bit is on, it is the smallest
506 // possible value
507 // (0 if unsigned)
508
509 // fill all user type depending values: min and max cooked values and
510 // fractional bit coefficients note: we loop over one of the maps only, but
511 // initCoefficients() will fill all maps!
512 boost::fusion::for_each(_minCookedValues, initCoefficients(this));
513 }
514
515 /********************************************************************************************************************/
517 /********************************************************************************************************************/
518} // namespace ChimeraTK
The fixed point converter provides conversion functions between a user type and up to 32 bit fixed po...
bool operator==(const FixedPointConverter &other) const
Compare two fixed point converters.
int getFractionalBits() const
Read back the fractional bits the converter is using.
FixedPointConverter(std::string variableName, unsigned int nBits=32, int fractionalBits=0, bool isSignedFlag=true)
The constructor defines the conversion factor.
bool operator!=(const FixedPointConverter &other) const
UserType scalarToCooked(RawType const &raw) const
Inefficient convenience function for converting a single value to cooked.
RawType toRaw(UserType cookedValue) const
Conversion function from type T to fixed point.
bool isSigned() const
Read back wether the conversion is using signed values.
unsigned int getNBits() const
Read back the number of bits the converter is using.
void vectorToCooked(const RAW_ITERATOR &raw_begin, const RAW_ITERATOR &raw_end, const COOKED_ITERATOR &cooked_begin) const
Conversion function from fixed-point values to type T.
boost::fusion::map< boost::fusion::pair< int8_t, TargetType >, boost::fusion::pair< uint8_t, TargetType >, boost::fusion::pair< int16_t, TargetType >, boost::fusion::pair< uint16_t, TargetType >, boost::fusion::pair< int32_t, TargetType >, boost::fusion::pair< uint32_t, TargetType >, boost::fusion::pair< int64_t, TargetType >, boost::fusion::pair< uint64_t, TargetType >, boost::fusion::pair< float, TargetType >, boost::fusion::pair< double, TargetType >, boost::fusion::pair< std::string, TargetType >, boost::fusion::pair< Boolean, TargetType >, boost::fusion::pair< Void, TargetType > > table
Exception thrown when a logic error has occured.
Definition Exception.h:51
boost::fusion::map< boost::fusion::pair< int8_t, int8_t >, boost::fusion::pair< uint8_t, uint8_t >, boost::fusion::pair< int16_t, int16_t >, boost::fusion::pair< uint16_t, uint16_t >, boost::fusion::pair< int32_t, int32_t >, boost::fusion::pair< uint32_t, uint32_t >, boost::fusion::pair< int64_t, int64_t >, boost::fusion::pair< uint64_t, uint64_t >, boost::fusion::pair< float, float >, boost::fusion::pair< double, double >, boost::fusion::pair< std::string, std::string >, boost::fusion::pair< Boolean, Boolean >, boost::fusion::pair< Void, Void > > userTypeMap
Map of UserType to value of the UserType.
int32_t DEPRECATED_FIXEDPOINT_DEFAULT
@ raw
Raw access: disable any possible conversion from the original hardware data type into the given UserT...
STL namespace.
std::string to_string(const std::string &v)
static void impl(const FixedPointConverter &fpc, const RAW_ITERATOR &raw_begin, const RAW_ITERATOR &raw_end, COOKED_ITERATOR cooked_begin)
static void impl(const FixedPointConverter &fpc, const RAW_ITERATOR &raw_begin, const RAW_ITERATOR &raw_end, COOKED_ITERATOR cooked_begin)