Valuelist's emergence and its application practice (1)

xiaoxiao2021-03-06  60

Valuelist's emergence and its application practice

Keywords: MML Typelist Valuelist Refholder TypeHolder

VALUELIST is a C tool for operating a large group of values, like Loki :: Typelist, like the same operations, Valuelist provides the same operation. In fact, Valuelist's name is from Typelist, which is summarized in work practice, which is used to solve practical technical issues, not just demonstrate C template skills.

1.1 Valuelist's necessity

Sometimes you have to write the same code for each value object, and Template can't help. To illustrate this problem, let's take a look at the technical issues that Valuelist needs to solve the first application.

MML: Human-machine interaction language, a protocol language in plain text format, in general, is similar to its command format:

: = , = [, ....];

CMD is a command word that can be easily understood and the function name in C is quite. CMD is typically separated by ":" and back parameters;

Arg1-name is the parameter name, which can be understood as the shape parameter in the function declaration in C . Parameter names are typically separated by "=" and back parameter values;

Arg1-value is the parameter value, which can be understood as a solid parameter incorporated in the function call in C . The parameter value is generally separated by "," and other parameter names - value

The entire MML command is typically ";"; "

In the author's work environment, you often need to read the string of the MML format from the outside (standard input, network, file, etc.), extract the contents of each parameter value, modify the value of the variable corresponding to the memory in the memory. We have defined the packaging class of the mml command to extract parameter names and parameter values:

Class MmlCommand

{

PUBLIC:

Bool Hasargument (const st :: string & arg) const; // Is there an input parameter name

Std :: string value (const st :: string & arg) const; // Extract the parameter value corresponding to the input parameter name

......

}

Now you need to get the following structure in the MML command input from the outside:

Struct Almdata

{

Long devcsn;

Long almid;

Std :: string nename;

...

}

We define the following functions to implement this feature

Void Convert (Const Mmlcommand & Cmd, Almdata & Alm)

{

IF (cmd.hasargument ("devcsn")))

Convert_Value (cmd.value ("devcsn"), alm.devcsn;

IF (cmd.hasargument ("alarmid"))

Convert_Value (cmd.value ("alarmid"), Alm.Alarmid;

IF (cmd.hasargument ("nename"))

Alm.nename = cmd.value ("NENAME");

......

}

Unfortunately, each time you use different data types to extract parameter values ​​in the MML command We have to repeat the above functions, and the functional body processing extracts the code of each member variable, but we have no way to eliminate this. Repeat.

Of course we can define such template functions Template

Void GetValue (Const Mmlcommand & Cmd, Const std :: string & arg1, t1 & arg2, const st: string & arg2, t2 & V2, const st: string & arg3, t3);

You can use the following simple call to the repetition of the CONVERT function in the program:

GetValue (CMD, "DEVCSN", Alm.devcsn, "Alarmid", Alm.Alarmid, "Nename", alm.nename

However, the number of GetValue's template parameters is impossible, which means we have to prepare a getValue template function with different parameters.

The insertion extraction operation of ValuelIST in the binding stream will enable the getValue function to be truly meta, which can achieve the function of processing any number of parameter values. Of course, Valuelist will bring more benefits.

1.2 implementation technology

The application environment of the MML protocol first will be thrown, starting with the definition of Valuelist and the simple application.

1.2.1 definition of Valuelist

Define Valuelist in Typelist

Tempate Class Valuelist

{

PUBLIC:

TYPEDEF T1 First_Value_Type;

TypedEf T2 SECOND_VALUE_TYPE;

T1 m_VAL1;

T2 m_VAL2;

Valuelist (Const T1 & V1, Const T2 & V2): M_VAL1 (V1), M_VAL2 (V2) {}

}

In this way, as long as the template parameter T2 is also a valuelist type, the recursive definition can save any number of data types. At the same time as Typelist, you also need an end type flag. Therefore, the following structure is defined as the end sign:

struct null_type {};

Also define a convenient function to generate VLAUELIST, repeat calling this function can connect the internal number (complex) data structure and a valuelist:

Template

Valuelist Makevaluelist (Const T1 & V1, Const T2 & V2)

{

Return Valuelist (V1, V2);

}

1.2.2 Valuelist flow insert operator and application example

First, consider the implementation of Valuelist to stream, very simple:

Template

Std :: basic_ostream & operator << (std :: basic_ostream & out, const value) & value)

{

/ / Responsible for the output of a single variable, if it is the basic type int, floag, const char *, etc.

OUT << value.m_va1;

// Removal output Other values

Return Out << value.m_val2;

}

Because Valuelist ended up in a null_type, we need to define an output operation of null_type, otherwise the above function will compile errors when used.

Template std :: basic_ostream & operator << (std :: basic_ostream & out, null_type)

{

// No need to do it, return directly

Return Out;

}

Applying the above implementation, we can output any quantity (complex) data structure. The following is an example.

Struct mydata {

INT F1;

Long F2;

Double F3;

Std :: string f4

Char f5;

}

Mydata data;

SD :: Cout << makevaluelist (Data.f1, makevaluelist (data.f2, makevaluelist (data.f4, makevaluelist (data.f5, null_type ())))))))))))))))

In this way, you can output any of the contents of any complex data structure without writing more code. The nested definition of Makevaluelist above can be replaced by macro, as follows:

#define vlist_1 (x) makevaluelist (x, null_type ()))

#define VLIST_2 (x1, x2) makevaluelist (x1, vlist_1 (x2))

#define VLIST_3 (X1, X2, X3) Makevaluelist (x1, Vlist_2 (x2, x3))

#define Vlist_4 (x1, x2, x3, x4) makevaluelist (x1, vlist_3 (x2, x3, x4)))

#define VLIST_5 (X1, X2, X3, X4, X5) Makevaluelist (x1, Vlist_4 (X2, X3, X4, X5)))

...

The above last output statement can be simplified:

Std :: Cout << VLIST_5 (Data.f1, Data.f2, Data.f3, Data.f4, Data.f5);

1.2.3 Flow Extraction Operation Bringing Complexity and Refholder Application

Now consider extracting the value of each unit of Valuelist from the stream, and its implementation is relatively complex:

Template

Std :: std :: basic_istream & operator >> (std :: std :: basic_istream & in, valuelist data)

{

/ / First restore the value value of a single unit

In >> Data.m_VAL1;

// Recursively restore the value of other units

In >> Data.m_VAL2;

}

The same reason, need to do special treatment for null_type

Template

Std :: std :: basic_istream & operator >> (std :: std :: basic_istream & in, null_type)

{

/ / Return directly

Return IN;

}

Please pay attention to why the above function is the type of parameter, why can't it be a const reference? Because we need to modify the value of its member variables through the flow extraction operator. Why can't it be a reference type? Because the reference type will bring us the inconvenience. Still the example in the previous section, to extract the value of the Data variable from the standard input stream, we can write: std :: cin >> VLIST_5 (Data.f1, Data.f2, Data.f3, Data.f4, Data.f5)

Because VLIST_5 returns a temporary variable, if the second parameter of the Operator >> Operation function is a non-const reference, it will result in compilation issues. Unless we can define a left value variable, the return value type of VLIST_5 is:

Valuelist >>>>

Such a complex type requires a user display to define its variable is basically unfair, not to mention in practical applications, more complex Valuelist, so we can only use Valuelist using values.

Next we must solve a problem with the value transfer method.

Std :: CIN >> VLIST_5 (Data.f1, Data.f2, Data.f3, Data.f4, Data.f5) This operation modifies a field in the interior of the temporary variable returned by VLIST_5 according to the content entered in the stream, and There is no value in the memory variable Data, and the latter is what we want. In order to solve this problem, a very natural idea is to modify the definition of VLAUELIST:

Template

Class Valuelist

{

PUBLIC:

T1 & M_VAL1;

T2 m_VAL2;

Valuelist (T1 & V1, T2 V2): M_VAL1 (V1), M_VAL2 (V2) {}

}

If this is the definition, std :: cin >> VLIST_5 (Data.f1, Data.f2, Data.f3, Data.f4, Data.f5) can change the value of the DATA variable to achieve our goal. Obviously, it requires Valuelist's first constructor that cannot be a right value (constant, temporary variable, etc.), which limits the Operator << operation to a large extent, because this operation is not limited to a left worth it. In practical applications, the input and output of the list is more, and still as an example, consider the following method:

Std :: List DataList;

// If you modify the definition of Valuelist as above, the compilation failed here because the non-Const reference cannot be initialized with a temporary variable.

Std :: cout << VLIST_1 (DataList.size ());

Unless we use this:

INT SZ = DATALIST.SIZE ();

Std :: Cout << VLIST_1 (SZ);

This limitation above is clearly unsatisfactory. Therefore, such modifications will bring ease of use, not a good solution. In fact, there is a contradiction here. We only need to save a copy or const reference in the Valuelist in the insertion operation, and a non-Const reference is required in the flow extraction operation, and the convenience of use is required. Their behavior is exactly the same. This seems uncoordinated contradictions can be resolved: it is to copy a reference type into a value type for copy replication. Its main purpose is to solve the problem that the current C does not support reference types, resulting in the template There are some inconveniences, it can be used here. It implements:

Template

Class Refholder {

T & M_REF;

PUBLIC:

Refholder (T & REF): m_ref (ref) {}

Operator t & () const {return m_ref;}

Refholder & operator = (const t & v) {m_ref = v;}

}

For any of the definitions of the template class, you can define a corresponding template function to facilitate generating an instance of this template class, we define the following convenience functions:

Template

Refholder Byref (T & Ref)

{

Return Refholder (REF);

}

With this definition of this refholder we define a MakeValuelIst's overload function:

Template

Valuelist , T2> MakevaluelIst (T1 & Ref, Const T2 & T2)

{

Return Valuelist , T2> (Byref (REF), T2);

}

Using the resolution of the overload function, let the compiler automatically select whether to use the refholder (in the best way, it is displayed to derive the CONST reference type corresponding to the given type and the non-const reference type, according to these two Type definition parameters, similar: makevaluelist :: Reference, ....), Makevaluelist :: const_reference, .... here also requires the introduction of various types of derivation template classes, relatively complex , But more accurate, Typetraits are implemented in the Loki library, you can refer to), so our code doesn't need any changes, of course, you need to define the flow insertion extraction of the refholder:

Template

Std :: std :: basic_ostream & operator << (std :: std :: basic_ostream & out, const refholder & data)

{

Return Out << (T &) DATA;

}

Template

Std :: std :: basic_istream & operator >> (std :: std :: basic_istream & in, refholder data)

{

RETURN IN >> (T &) DATA;

}

1.2.4 Valuelist's Complete Definition and Use

Template

Class Refholder

{

T & M_REF;

PUBLIC:

Refholder (T & REF): m_ref (ref) {}

Operator t & () const {return m_ref;}

Refholder & operator = (const t & v) {m_ref = v;}

}

Template Refholder Byref (T & REF)

{

Return Refholder (REF);

}

Template

Std :: std :: basic_ostream & operator << (std :: std :: basic_ostream & out, const refholder & data)

{

Return Out << (T &) DATA;

}

Template

Std :: std :: basic_istream & operator >> (std :: std :: basic_istream & in, refholder data)

{

RETURN IN >> (T &) DATA;

}

Class null_type {};

Tempate Class Valuelist

{

PUBLIC:

T1 m_VAL1;

T2 m_VAL2;

Valuelist (Const T1 & V1, Const T2 & V2): M_VAL1 (V1), M_VAL2 (V2) {}

}

Template

Valuelist Makevaluelist (Const T1 & V1, Const T2 & V2)

{

Return Valuelist (V1, V2);

}

Template

Valuelist , T2> MakevaluelIst (T1 & Ref, Const T2 & T2)

{

Return Valuelist , T2> (Byref (REF), T2);

}

Template

Std :: basic_ostream & operator << (std :: basic_ostream & out, const value) & value)

{

OUT << value.m_va1;

Return Out << value.m_val2;

}

Template

Std :: Basic_OStream & operator << (std :: basic_ostream & out, null_type)

{

Return Out;

}

Template

Std :: std :: basic_istream & operator >> (std :: std :: basic_istream & in, valuelist data)

{

In >> Data.m_VAL1;

In >> Data.m_VAL2;

}

Template

Std :: std :: basic_istream & operator >> (std :: std :: basic_istream & in, null_type)

{

Return IN;

}

1.2.5 Application of MML Command Format

Now we need to use existing foundations to resolve and assemble the MML commands. In fact, we only need to convert a string of the "arg_name = arg_value" format in the MML command to the value of an internal variable. This also needs to take a name for each internal variable, it is to correspond to the arg_name in the MML command. So define a NameValue.

Template

Struct NameValue

{

Const std :: string m_name;

T M_Value;

NameValue (const st :: string & n, t v): m_name (n), m_value (v) {}

}

It also defines its convenience function, the same principle, and use the function overload function to automatically select whether to use refholder:

Template

NameValue MakenameValue (Const std :: string & name, const t & v)

{

Return NameValue (Name, V);

}

Template

NameValue > MakenameValue (const st: string & name, t & v)

{

Return NameValue > (Name, Byref (V));

}

Then implement its flow runoff function:

Template

Std :: std :: basic_ostream & operator << (std :: std :: basic_ostream & out, const namevalue & data)

{

Return Out << Data.m_name << "=" << Data.m_Value << ","

}

Template

Std :: std :: basic_istream & operator >> (std :: std :: basic_istream & in, namevalue data)

{

/ / According to the MML format, use "=" and "," to extract the Name and Value values.

Std :: string name; ......

Std :: String Value;

......

IF (name == DATA.M_NAME)

{

Std :: istringstream Str (Value);

// Go back to the basic type from the extraction operator in the stream.

Str >> Data.m_Value;

}

Return IN;

}

Finally, we need a macro that makes it easy to generate NameValue, similar to the definition of VLIST_XXX:

#define nvlist_1 (n, v) makevaluelist (MakenameValue (N, V), NULL_TYPE ())

#define NVLIST_2 (N1, V1, N2, V2) Makevaluelist (MakenameValue (N1, V1), NVLIST_1 (N2, V2))

.

Still using the previous MyData example, do the following definition:

#define mydata_mml (x) NVLIST_5 ("INT", X.F1, "Long", X.f2, "Double", X.f3 "," String ", X.f4," char ", x.f5)

Std :: cout << mydata_mml (data);

Std :: cin >> mydata_mml (data);

In fact, we have similar to this three lines of code for any type of application, using nvlist_xx to define a macro for use types, and then use it directly.

1.2.6 Further considering the processing of the mml parameter position independent

The parameter order in the MML command in the actual application is irrelevant. The previous implementation cannot support this feature, in order to solve the problem of unrelated parameter order, you need to reconsider the parsing process of MML.

Also utilize the recursive properties of Valuelist: If the MML parameters of the current resolved and the name of the current unit do not match, try to match the next unit.

First, read a single nameValue value, return value indicates whether input and actual NameValue match.

Template

Bool GetValue (Const std :: string & name, std :: string & value, namevalue data)

{

IF (Name! = DATA.M_NAME) Return False;

Std :: istringstream Str (Value);

Str >> Data.m_Value;

Return True;

}

Based on the extracted parameter name, recursive matching items in Valuelist , if there is no match, may be derived with NULL_TYPE, so define a special function

Template

Bool GetValue (Const std :: string & name, const st :: string & value, valuelist , t2> data)

{

IF (! getValue (name, value, data.m_val1) return getValue (name, value, data.m_val2);

Return True;

}

Bool getValue (const st: string & name, const st :: string & value, null_type)

{

Return False;

}

Then read the parameters of each MML format from the external stream, and the resulting parameter name and parameter value are used to match the above function. Of course, it is assumed that all the values ​​in the valuelist are named, if not the compilation phase Report error.

Template

Std :: std :: basic_istream & operator >> (std :: std :: basic_istream & in, valuelist , t2> data) {

While (in .rdbuf () -> in_avail ())

{

/ / According to the MML format, use "=" and "," to extract the Name and Value values.

Std :: string name;

....// Get Name

Std :: String Value;

....// Get VLAUE

GetValue (Name, Value, Data);

}

}

The above sample code is not perfect, and the end of the stream input is not too reasonable. It can be considered to read the end identity of the MML command ";" or as long as any of the MML parameters and the value in the value of Valuel do, return, or A comparison of parameters that fix the length of VALUELIST. For the way to get the length of the Valuelist, Typelist can be referred to, which can be said to be exactly the same, directly to apply it. These methods are easy to implement, which is not listed here.

1.2.7 Problems from other details

In practical applications, there are also special processing of char types. The use of char types inside the program is generally the same use with other integer, but the range of value is different, however the CHAR type will perform insertion extraction operations from the stream. It may be relatively large with the difference we imagined:

CHAR C = 10;

Std :: cout << C;

We hope that the last output is a string "10", but the actual result is indeed an invisible character. Therefore, special processing is required for the char type. This is discussed, and the actual application is likely to see a type to see another type of operation, and we need to replace the type.

Similar to the template to achieve:

Template

Class TypeHolder

{

T2 m_T2;

PUBLIC:

TypeHolder (Const T2 & T2): M_T2 (T2) {}

Operator const t1 & () {return (t1) m_t2;}

TypeHolder & Operator = (const t1 & v) {m_t2 = v;}

}

As in front, define a convenient function and use the function overload mechanism to automatically select whether to use Refholder

Template

Inline TypeHold ByType (Const T2 & T)

{

Return TypeHolder (T);

}

Template

Inline TypeHolder > ByType (T2 & T)

{

Return TypeHolder > (Byref (t));

}

Similarly, you can use it directly as long as it defines its input and output stream operators.

Template

Std :: Basic_OStream & operator << (std :: basic_ostream & out, const typeholder & data)

{

Return OUT << (T1) (DATA);

}

template std :: basic_istream & operator << (std :: basic_istream & in, TypeHolder data)

{

T1 TMP;

in >> TMP;

Data = TMP;

Return IN;

}

The above implementation is too simple, mainly reflected in the conversion between types, and it is likely that some types of types cannot be directly converted. The more way to define the conversion action as TypeHolded template parameters, from the outside world, of course we provide one Reasonable default implementation. Perfect implementation is not related to the topics here, will not explain it here.

In addition, the extraction of std :: string is also relatively special. By default, if there is a space in the middle of the string in the input stream, the extraction operation will cause the string to be truncated. This time we use the method of special GetValue functions to solve:

Template <>

Bool getValue (const st :: string & name, std :: string& value, namevalue data)

{

IF (Name! = DATA.M_NAME) Return False;

Data.m_Value = value;

Return True;

}

Finally, improve the previous example, we need to treat the char fields in MyData as int use, redefine:

#define mydata_mml (x) NVLIST_5 ("Int", Data.f1, "Long", Data.f2, "Double", DATA.F3, "String", Data.f4, "char", ByType (DATA .f5))

Std :: cout << mydata_mml (data);

Std :: cout >> MyData_mml (data);

So far, we can completely share the implementation code for the MML codec and memory data automatic mapping. The simple repetition code that can be used is completely replaced with the above three lines of code.

1.2.8 final summary

The Valuelist of the insertion extraction operation of the binding stream can greatly simplify the process from the MML command and the assembly of the MML command. In fact, this idea can also be applied to other format parsing, such as the analysis of the external input parameters, even asn .1 codec, similar means, it can also be implemented. Valuelist is of course a broader purpose. When it is combined with containers in the STL library, we can use a more powerful standard container.

转载请注明原文地址:https://www.9cbs.com/read-113054.html

New Post(0)