How to use C++ Builder OPENARRAY in a custom function - c++

I would like to use OPENARRAY (or an alternative, if you have one) to pass multiple placeholder variables into my function. I know that it can be used for String::Format in this way:
UnicodeString Text1 = "abc";
int Num2 = 1;
String::Format("Some %s and %d", OPENARRAY(TVarRec, (Text1, Num2));
What I would like is to use Text1 and Text2 variables in something like this:
MyFunction("Some %Txt1 and %Num2", OPENARRAY(TVarRec, ("%Txt1", Text1, "%Num2", Num2));
Or perhaps:
MyFunction(OPENARRAY(TVarRec, ("Some %Txt1 and %Num2", "%Txt1", Text1, "%Num2", Num2));
So it would accept the text and replace the placeholder variables with appropriate variable content.
What I don't know is how do I read OPENARRAY parameter content from MyFunction.
So the function would look like:
UnicodeString MyFunction(UnicodeString Txt, ?WHAT-HERE?)
{
// read openarray here and replace vars
return StringReplace(Txt, ?WHAT-HERE?);
}
So I don't know how do I accept the OPENARRAY variables. Also, if you have an alternative solution to passing placeholder, variable in similar manner (without the use of C++11), that would also be welcome.

When passing a Delphi-style Open Array to a function, two things are actually passed:
a pointer to the first element of the array
the index of the last element of the array (NOT the length of the array, as you might expect!).
So, in your example, you can declare your function like this:
UnicodeString MyFunction(UnicodeString Txt, const TVarRec *Values, const int Values_High)
And then loop through the array using normal pointer arithmetic.
TVarRec can hold many different data types, so you have to look at its VType field to know what kind of data it is actually referencing, and then access the appropriate data field. Some values (integers and single characters) are stored directly in the TVarRec itself, while others (strings and other class types) are referenced by pointer instead.
For example:
UnicodeString MyFunction(UnicodeString Txt, const TVarRec *Values, const int Values_High)
{
if (((Values_High + 1) % 2) != 0)
throw Exception("uneven number of values!");
for (int index = 0; index <= Values_High; index += 2)
{
String OldValue, NewValue;
switch (Values[index].VType)
{
case vtString:
OldValue = * static_cast<const ShortString*>(Values[index].VString);
break;
case vtPChar:
OldValue = Values[index].VPChar;
break;
case vtPWideChar:
OldValue = Values[index].VPWideChar;
break;
case vtAnsiString:
OldValue = * static_cast<const AnsiString*>(Values[index].VAnsiString);
break;
case vtWideString:
OldValue = * static_cast<const WideString*>(Values[index].VWideString);
break;
case vtUnicodeString:
OldValue = * static_cast<const UnicodeString*>(Values[index].VUnicodeString);
break;
default:
throw Exception("illegal value type at index %d!", ARRAYOFCONST(( index )) );
}
switch (Values[index+1].VType)
{
case vtInteger:
NewValue = Values[index+1].VInteger;
break;
case vtBoolean:
NewValue = Values[index+1].VBoolean;
break;
case vtChar:
NewValue = Values[index+1].VChar;
break;
case vtExtended:
NewValue = * static_cast<const Extended*>(Values[index+1].VExtended);
break;
case vtString:
NewValue = * static_cast<const ShortString*>(Values[index+1].VString);
break;
case vtPChar:
NewValue = Values[index+1].VPChar;
break;
case vtWideChar:
NewValue = Values[index+1].VWideChar;
break;
case vtPWideChar:
NewValue = Values[index+1].VPWideChar;
break;
case vtAnsiString:
NewValue = * static_cast<const AnsiString*>(Values[index+1].VAnsiString);
break;
case vtCurrency:
NewValue = * static_cast<const Currency*>(Values[index+1].VCurrency);
break;
case vtVariant:
NewValue = * static_cast<const Variant*>(Values[index+1].VVariant);
break;
case vtWideString:
NewValue = * static_cast<const WideString*>(Values[index+1].VWideString);
break;
case vtInt64:
NewValue = * static_cast<const __int64*>(Values[index+1].VInt64);
break;
case vtUnicodeString:
NewValue = * static_cast<const UnicodeString*>(Values[index+1].VUnicodeString);
break;
default:
throw Exception("illegal value type at index %d!", ARRAYOFCONST(( index )) );
}
Txt = StringReplace(Txt, OldValue, NewValue, TReplaceFlags() << rfReplaceAll);
}
return Txt;
}
MyFunction("Some %Txt1 and %Num2", OPENARRAY(TVarRec, ("%Txt1", Text1, "%Num2", Num2)) );
On a side note, when a function takes an open array of TVarRec values, you should use the ARRAYOFCONST() macro instead of the OPENARRAY() macro directly, eg:
String::Format("Some %s and %d", ARRAYOFCONST(( Text1, Num2 )) );
MyFunction("Some %Txt1 and %Num2", ARRAYOFCONST(( "%Txt1", Text1, "%Num2", Num2 )) );

Related

Matlab crash after returning from function that calls MEX

I'm creating a MEX to find a path between two nodes using the A* algorithm. The code works as expected, retrieves the right result and everything seems to be fine, but when I return from the method that calls the MEX, Matlab simply shuts itself down.
As I know sometimes Matlab creates a pointer to the same memory address when the user tries to create a copy of a variable (I.E. A = 3; B = A, then A and B are pointing to the same memory address, even though Matlab shows them as 2 independent variables) I used an old trick that consists on performing an operation directly to the copy of the variable, even if its a silly one, Matlab will think that the two variables are no longer the same and will create a copy of it (I.E. A = 3; B = A; B = B+0, now A and B are stored as different and independent variables).
So the only way that I have found to fix the issue is to do what is shown below in function getWP:
function testAStarC()
% load the map of the area
load('epsp0_2_nav.mat');
% Set the initial node
initNode = '16x21';
% Set the target node
finalNode = '-15x54';
% Select Heuristic
heuristic = 'Manhattan';
% Create a target location (targetX, targetY, targetAngle)
targetCoords = [-15*110 54*110 0.15];
% Function that hosts the call to the MEX
wp = getWP(map, initNode, finalNode, targetCoords, heuristic);
disp('If you remove the line cellNodes{keyID}.x = cellNodes{keyID}.x; from getWP ...
I wont reach this line');
disp(['Route with ',num2str(length(wp)),' wp found']);
disp('done');
function waypointsList = getWP(map, initNode, finalNode, targetCoords, heuristic)
% HashMap containing the nodes (this is a Java hashmap)
nodesHash = map.navMap.nodes.nodes;
keys = nodesHash.keys();
numNodes = length(keys);
cellNodes = cell(1,numNodes);
% Parse the nodes from the HashMap to Cells as the MEX seems to be
% unable to read directly from the Java HashMap
for keyID=1:numNodes
cellNodes{keyID} = nodesHash(keys{keyID});
%---------------------------------------------------------
% WITHOUTH THIS MATLAB CRASHES WHEN RETURNING FROM GETWP
%---------------------------------------------------------
% We need this to force Matlab to create a new copy of the content,
% otherwise will send a pointer aiming to the HashMap and crash when
% returning from getWP.
cellNodes{keyID}.x = cellNodes{keyID}.x;
end
waypointsList = AStar(cellNodes, initNode, finalNode, targetCoords, heuristic, 1);
disp('I am not crashing here if you remove cellNodes{keyID}.x = cellNodes{keyID}.x');
My first thought was that I was doing something wrong to "cellNodes" inside the MEX and this was causing Matlab to crash, but I am not performing any operations using directly the input parameter. This is the constructor for the Node class:
Node.cpp
Node::Node(mxArray *cellElement)
{
double *xIn;
double *yIn;
char strIn[15];
double *posXIn;
double *posYIn;
double *weightIn;
double *tempVal;
size_t numCellElms;
mxArray *cellElement2;
numCellElms = mxGetNumberOfFields(cellElement);
size_t size;
for (int cellItem = 0; cellItem < numCellElms; cellItem++)
{
cellElement2 = mxGetCell(cellElement,cellItem);
if (cellItem == 0)
{
xIn = mxGetPr(cellElement2);
memcpy(tempVal,xIn,sizeof(double));
gridX = int(*tempVal);
}
if (cellItem == 1)
{
yIn = mxGetPr(cellElement2);
memcpy(tempVal,yIn,sizeof(double));
gridY = int(*tempVal);
}
if (cellItem >= 2 && cellItem < 10)
{
mwSize buflen = mxGetN(cellElement2)*sizeof(mxChar)+1;
if (buflen <= 1)
{
connections[cellItem-2][0] = '\0';
}
else
{
mxGetString(cellElement2, strIn, buflen);
memcpy(&connections[cellItem-2][0], strIn, buflen);
}
}
if (cellItem == 10)
{
posXIn = mxGetPr(cellElement2);
memcpy(&posX,posXIn,sizeof(double));
}
if (cellItem == 11)
{
posYIn = mxGetPr(cellElement2);
memcpy(&posY,posYIn,sizeof(double));
}
if (cellItem == 12)
{
posXIn = mxGetPr(cellElement2);
memcpy(&wpX,posXIn,sizeof(double));
}
if (cellItem == 13)
{
posYIn = mxGetPr(cellElement2);
memcpy(&wpY,posYIn,sizeof(double));
}
if (cellItem == 14)
{
weightIn = mxGetPr(cellElement2);
memcpy(&weight,weightIn,sizeof(double));
}
}
sprintf(xStr,"%i",gridX);
sprintf(yStr,"%i",gridY);
sprintf(key,"%ix%i",gridX,gridY);
}
And this is how I initialize the Nodes list inside the AStar.cpp
// Create nodes in the nodes hash
mxArray *cellElement;
std::map<std::string, Node> nodesHash;
for (int nodeID=0; nodeID < numberOfNodes; nodeID++)
{
cellElement = mxGetCell(prhs[0], nodeID);
Node n(cellElement);
nodesHash[n.key] = n;
}
Form now on nothing uses prhs[0] anymore, as nothing has altered the content of the prhs[0] (variable containing the pointer to the Matlab variable cellNodes), this variable should be exactly the same after leaving the MEX.
From here I have two questions:
If nothing is altering the content of the first parameter, why should it crash when returning from getWP?
Is there a more elegant way of forcing Matlab to create a real copy of a variable?
Thanks!
*Edit1: Using Matlab 2015b in Windows10 64 bit.
SOVED by #CrisLuengo and #JamesTursa.
1) If nothing is altering the content of the first parameter, why should it crash when returning from getWP?
[Node.cpp] 'double* tempVal' was not allocated, although the code worked, this was possibly messing with the integrity of prhs[0] thus causing Matlab to crash when returning from the function that invoked the MEX.
The solution was to declare tempVal as 'double tempVal[1]'.
After that, the testAStarC.m the line 'cellNodes{keyID}.x = cellNodes{keyID}.x;' can be removed without causing the error.
Although unrelated to the crash, the use of memcpy to get scalar doubles has been replaced with mxGetScalar().
Node constructor:
Node::Node(mxArray *cellElement)
{
char strIn[15];
double tempVal[1];
size_t numCellElms = mxGetNumberOfFields(cellElement);
mwSize buflen;
for (int cellItem = 0; cellItem < numCellElms; cellItem++)
{
mxArray *cellElement2 = mxGetCell(cellElement,cellItem);
switch (cellItem)
{
case 0:
gridX = (int)mxGetScalar(cellElement2);
break;
case 1:
gridY = (int)mxGetScalar(cellElement2);
break;
case 2:
case 3:
case 4:
case 5:
case 6:
case 7:
case 8:
case 9:
buflen = mxGetN(cellElement2)*sizeof(mxChar)+1;
if (buflen <= 1)
connections[cellItem-2][0] = '\0';
else
{
mxGetString(cellElement2, strIn, buflen);
memcpy(&connections[cellItem-2][0], strIn, buflen);
}
break;
case 10:
posX = mxGetScalar(cellElement2);
break;
case 11:
posY = mxGetScalar(cellElement2);
break;
case 12:
wpX = mxGetScalar(cellElement2);
break;
case 13:
wpY = mxGetScalar(cellElement2);
break;
case 14:
weight = mxGetScalar(cellElement2);
break;
}
}
sprintf(xStr,"%i",gridX);
sprintf(yStr,"%i",gridY);
sprintf(key,"%ix%i",gridX,gridY);
}
testAStarC.m
function waypointsList = getWP(map, initNode, finalNode, targetCoords, heuristic)
% HashMap containing the nodes (this is a Java hashmap)
nodesHash = map.navMap.nodes.nodes;
keys = nodesHash.keys();
numNodes = length(keys);
cellNodes = cell(1,numNodes);
% Parse the nodes from the HashMap to Cells as the MEX seems to be
% unable to read directly from the Java HashMap
for keyID=1:numNodes
cellNodes{keyID} = nodesHash(keys{keyID});
end
waypointsList = AStar(cellNodes, initNode, finalNode, targetCoords, heuristic, 1);
2) Is there a more elegant way of forcing Matlab to create a real copy of a variable?
No, the best you can do is try the assignment trick.

C Mysql driver in C++: Fetch field only (function returns mixed type)

I'm trying to implement fetching data function from mysql just for one field instead whole row.
auto FetchField(char *value, MYSQL_FIELD *field) {
if (value == NULL) {
return nullptr;
}
switch(field->type) {
case MYSQL_TYPE_FLOAT:
case MYSQL_TYPE_DOUBLE:
return atof(value);
case MYSQL_TYPE_LONG:
case MYSQL_TYPE_TINY:
case MYSQL_TYPE_INT24:
return atoi(value);
default:
return value;
}
}
And usage function:
auto QueryField(MYSQL *db, const char *query, ...) {
va_list params;
va_start(params, query);
StmtExec(db, query, params); // this calls mysql_query with params
va_end(params);
MYSQL_RES *res = mysql_store_result(db);
MYSQL_ROW row = mysql_fetch_row(res);
return FetchField(row[0], mysql_fetch_field(res));
}
So it shows error: deduced return type "double" conflicts with previously deduced type "std::nullptr_t"
I'd like avoid using mysql_fetch_row but how can I do it in other way? Just fetch one field instead of whole row.
I'd like my function will return automatic type or something like that (I know C++ has strict typing but maybe there's some way to implement it). Because now I'm doing there same (without defining datatype structs schema explicitly using nlohmann::json container).
nlohmann::json FetchRow(MYSQL_RES *res) {
int fields_count = mysql_num_fields(res);
MYSQL_ROW row = mysql_fetch_row(res);
nlohmann::json row_json;
mysql_field_seek(res, 0);
for(int j=0; j<fields_count; j++)
{
MYSQL_FIELD *field = mysql_fetch_field(res);
if (row[j] == NULL) {
row_json[field->name] = nullptr;
continue;
}
switch(field->type) {
case MYSQL_TYPE_FLOAT:
case MYSQL_TYPE_DOUBLE:
row_json[field->name] = atof(row[j]);
break;
case MYSQL_TYPE_LONG:
case MYSQL_TYPE_TINY:
case MYSQL_TYPE_INT24:
row_json[field->name] = atoi(row[j]);
break;
default:
row_json[field->name] = row[j];
break;
}
}
return row_json;
}
So I'd like to also reuse this method from FetchRow method as well if that's possible.
The point why I use C driver instead of C++ because I simply don't need to use classes and OOP in my project.
Is there any other way to implement what I want?
Thanks

D Lang: std.bitmanip read data into struct

I found this function: https://dlang.org/phobos/std_bitmanip.html#.read
T read(T, Endian endianness = Endian.bigEndian, R)(ref R range)
if (canSwapEndianness!T && isInputRange!R && is(ElementType!R : const(ubyte)));
But I can't understand how to make reading from data into structure.
Is it possible to use something like this?
struct MyType {
uint value;
this(ubyte act) { // wtf.peek!ubyte();
switch (act) {
case 0:
value = wtf.read!uint();
break;
case 1:
wtf.read!ubyte(); // seek
value = wtf.read!uint() | 0x7;
break;
...
}
}
}
...
buffer.read!MyType();
I don't fully understand the structure of your code - where does wtf come from?
It seems to me you've misunderstood what to pass to MyType's constructor - you probably should pass it the range, and read off of that with std.bitmanip.read. I might have misunderstood your code, though:
import std.range;
import std.bitmanip;
struct MyType {
uint value;
this(R)(auto ref R rng) if (isInputRange!R && is(ElementType!R : const(ubyte)))
{
auto act = rng.peek!ubyte;
switch (act) {
case 0:
value = rng.read!uint;
break;
case 1:
rng.read!ubyte;
value = rng.read!uint;
break;
// ...
default: assert(0);
}
}
}
unittest {
ubyte[] buffer = [0x01,0x12,0x34,0x56,0x78];
auto a = MyType(buffer);
assert(buffer.length == 0);
assert(a.value == 0x12345678);
}
As you can see, I simply call MyType's constructor with the array as parameter, but you could also wrap it in a read function if you really want to:
alias read = std.bitmanip.read;
T read(T : MyType, R)(auto ref R range)
if (isInputRange!R && is(ElementType!R : const(ubyte)))
{
return MyType(range);
}
unittest {
ubyte[] buffer = [0x01,0x12,0x34,0x56,0x78];
auto a = buffer.read!MyType;
assert(buffer.length == 0);
assert(a.value == 0x12345678);
}

Capping the value

I have a small script that defines the casting time for all classes in this minor project im working on however i have a few issues.
I want to place a cap on the max value however i'm getting errors!
this is the function i was referring to.
void Player::ApplyRatingMod(CombatRating combatRating, int32 value, bool apply)
{
float oldRating = m_baseRatingValue[combatRating];
m_baseRatingValue[combatRating] += (apply ? value : -value);
// explicit affected values
float const multiplier = GetRatingMultiplier(combatRating);
float const oldVal = oldRating * multiplier;
float const newVal = m_baseRatingValue[combatRating] * multiplier;
switch (combatRating)
{
case CR_HASTE_MELEE:
ApplyAttackTimePercentMod(BASE_ATTACK, oldVal, false);
ApplyAttackTimePercentMod(OFF_ATTACK, oldVal, false);
ApplyAttackTimePercentMod(BASE_ATTACK, newVal, true);
ApplyAttackTimePercentMod(OFF_ATTACK, newVal, true);
break;
case CR_HASTE_RANGED:
ApplyAttackTimePercentMod(RANGED_ATTACK, oldVal, false);
ApplyAttackTimePercentMod(RANGED_ATTACK, newVal, true);
break;
case CR_HASTE_SPELL:
//ApplyCastTimePercentMod(oldVal, false);
//ApplyCastTimePercentMod(newVal, true);
break;
default:
break;
}
UpdateRating(combatRating);
}
void Player::UpdateRating(CombatRating cr)
{
int32 amount = m_baseRatingValue[cr];
// Apply bonus from SPELL_AURA_MOD_RATING_FROM_STAT
// stat used stored in miscValueB for this aura
AuraEffectList const& modRatingFromStat = GetAuraEffectsByType(SPELL_AURA_MOD_RATING_FROM_STAT);
for (AuraEffect const* aurEff : modRatingFromStat)
if (aurEff->GetMiscValue() & (1 << cr))
amount += int32(CalculatePct(GetStat(Stats(aurEff->GetMiscValueB())), aurEff->GetAmount()));
if (amount < 0)
amount = 0;
SetUInt32Value(PLAYER_FIELD_COMBAT_RATING_1 + cr, uint32(amount));
bool affectStats = CanModifyStats();
switch (cr)
{
case CR_WEAPON_SKILL: // Implemented in Unit::RollMeleeOutcomeAgainst
case CR_DEFENSE_SKILL:
UpdateDefenseBonusesMod();
break;
case CR_DODGE:
UpdateDodgePercentage();
break;
case CR_PARRY:
UpdateParryPercentage();
break;
case CR_BLOCK:
UpdateBlockPercentage();
break;
case CR_HIT_MELEE:
UpdateMeleeHitChances();
break;
case CR_HIT_RANGED:
UpdateRangedHitChances();
break;
case CR_HIT_SPELL:
UpdateSpellHitChances();
break;
case CR_CRIT_MELEE:
if (affectStats)
{
UpdateCritPercentage(BASE_ATTACK);
UpdateCritPercentage(OFF_ATTACK);
}
break;
case CR_CRIT_RANGED:
if (affectStats)
UpdateCritPercentage(RANGED_ATTACK);
break;
case CR_CRIT_SPELL:
if (affectStats)
UpdateAllSpellCritChances();
break;
case CR_HIT_TAKEN_MELEE: // Implemented in Unit::MeleeMissChanceCalc
case CR_HIT_TAKEN_RANGED:
break;
case CR_HIT_TAKEN_SPELL: // Implemented in Unit::MagicSpellHitResult
break;
case CR_CRIT_TAKEN_MELEE: // Implemented in Unit::RollMeleeOutcomeAgainst (only for chance to crit)
case CR_CRIT_TAKEN_RANGED:
break;
case CR_CRIT_TAKEN_SPELL: // Implemented in Unit::SpellCriticalBonus (only for chance to crit)
break;
case CR_HASTE_MELEE: // Implemented in Player::ApplyRatingMod
case CR_HASTE_RANGED:
case CR_HASTE_SPELL:
break;
case CR_WEAPON_SKILL_MAINHAND: // Implemented in Unit::RollMeleeOutcomeAgainst
case CR_WEAPON_SKILL_OFFHAND:
case CR_WEAPON_SKILL_RANGED:
break;
case CR_EXPERTISE:
if (affectStats)
{
UpdateExpertise(BASE_ATTACK);
UpdateExpertise(OFF_ATTACK);
}
break;
case CR_ARMOR_PENETRATION:
if (affectStats)
UpdateArmorPenetration(amount);
break;
}
}
void Player::UpdateAllRatings()
{
for (uint8 cr = 0; cr < MAX_COMBAT_RATING; ++cr)
UpdateRating(CombatRating(cr));
}
You can notice how i want to cap the new value outcome to 32000.
Before it gets calculated to percentages!
I tried using
if(newVal > 32000)
newVal = 32000;
Which would normally do the job i guess, but i'm stuck with an error
E0137 expression must be a modifiable lvalue
On the line newVal = 32000;
Remove const qualifier from newVal
float newVal = m_baseRatingValue[combatRating] * multiplier;
You declare newVal as a constant (float const newVal = ...), so you're not allowed to assign a new value.
Removing const should work for you, e.g.
float newVal = m_baseRatingValue[combatRating] * multiplier;
if (newVal > 32000)
newVal = 32000;
...

How to avoid Switch Case & sscanf Function

I am working on C++. I am writing code for date format. Using with, we can get default date format from anyother date format. So I have found 240 date formats for this task. So M want to use switch case and sscanf function. Every case have sscanf function to separate day,month, year. So I need 240 cases and 240 sscanf function. Is there any method to avoid lot of swtich and sscanf? If you have any ideas, please let me know guys.
case 0:
sscanf(tsdate.c_str(),"%2d/%2d/%4d",&day,&month,&year);
break;
case 1:
sscanf(tsdate.c_str(),"%2d-%2d-%4d",&month,&day,&year);
break;
case 2:
sscanf(tsdate.c_str(),"%2d %2d %4d",&day,&month,&year);
break;
case 3:
sscanf(tsdate.c_str(),"%2d/%2d/%2d",&day,&month,&year);
coryear(year);
break;
case 4:
sscanf(tsdate.c_str(),"%2d/%2d/%2d",&year,&month,&day);
coryear(year);
break;
Like above, I want to put 240 cases and 240 sscanf. Please let me know how to avoid a lot of cases.
you can't avoid the switch cases but you can create multiple function to avoid sscanf:
void scanDayFirst(string format)
{
sscanf(tsdate.c_str(),format,&day,&month,&year);
break;
}
void scanMonthFirst(string format)
{
sscanf(tsdate.c_str(),format,&month,&day,&year);
break;
}
and so on.....
the result will be like this:
case 0:
scanDayFirst("%2d/%2d/%4d");
case 1:
scanDayFirst("%2d-%2d-%4d");
case 2:
scanDayFirst("%2d %2d %4d");
case 3:
scanDayFirst("%2d.%2d.%4d");
enum { ITEM_YEAR, ITEM_MONTH, ITEM_DAY, NUM_ITEMS };
struct date_format { char const *fmt; int items[NUM_ITEMS]; };
struct date_format const formats[] =
{ { "%2d/%2d/%4d", { ITEM_DAY, ITEM_MONTH, ITEM_YEAR } }
, { "%2d-%2d-%4d", { ITEM_MONTH, ITEM_DAY, ITEM_YEAR } }
/* etc. */
};
int parts[NUM_ITEMS]; /* Instead of year,month,day */
sscanf(tsdate.c_str(), formats[x].fmt,
&parts[formats[x].items[0]],
&parts[formats[x].items[1]],
&parts[formats[x].items[2]]);
You can extend this to have ITEM_NONE if you want to skip an item, or add extra items, and so on.
NB. If this is C++ then consider using stream input instead of sscanf.
Instead of enumerating all cases consider instead using a custom language for the format specification:
Date x = parseDate(user_input, "dd-mm-yyyy");
it will make the function shorter, easier to document and easier to use also improving the readability of the code that uses it. The idea is using codes like
yyyy ............. 4-digits year
yy ............... 2-digits year with automatic century computation
mm ............... 2-digits month
m ................ 1 or 2 digits month
dd ............... 2-digits day
d ................ 1 or 2 digits day
anything else .... mandatory character
A simple implementation could be
Date parseDate(const std::string& input, const std::string& format) {
const char *src = input.c_str();
const char *fmt = format.c_str();
int year=-1, month=-1, day=-1;
while (*fmt) {
if (!*src) throw invalid_date(input, format);
if (strncmp(fmt, "yyyy", 4) == 0) {
fmt += 4;
year = getInt(src, 4);
} else if (strncmp(fmt, "yy", 2) == 0) {
fmt += 2;
year = guess_century(getInt(src, 2));
} else if (strncmp(fmt, "mm", 2) == 0) {
fmt += 2;
month = getInt(src, 2);
} else if (fmt[0] == 'm') {
fmt += 1;
month = getInt(src, -1);
} else if (strncmp(fmt, "dd", 2) == 0) {
fmt += 2;
day = getInt(src, 2);
} else if (fmt[0] == 'd') {
fmt += 1;
day = getInt(src, -1);
} else {
if (src[0] != fmt[0]) throw invalid_date(input, format);
src++; fmt++;
}
}
if (*src || year == -1 || month == -1 || day == -1)
throw invalid_date(input, format);
return Date(year, month, day);
}
NOTE: untested