I. background
Business that requires database query and access in the project. When writing Database SQL statement code, there is no complicated formatting requirement, it is decided to use stream in the C ++ standard library to format SQL statements. There are two benefits:
1). type security
2). Easy to use
For example
Std: ostringstream OS;
Std: uint32_t id = 10;
OS <"select * from user_t where id =" <id;
SQL _excute (OS. str ());
Because the database needs to add single quotes to the input string parameters, you need to call an additional function to add single quotes when formatting string parameters, as shown below:
Std: string add_quote (const std: string & param)
{
Return "'" + param + "'";
}
Void print ()
{
Std: ostringstream OS;
Std: uint32_t id = 10;
Std: string name = "test_name ";
OS <"select * from user_t where id =" <id <"and"
<"Name =" <add_quote (name );
SQL _excute (OS. str ());
}
This solution can solve the problem, but it is very complicated to use. In particular, when there are many string parameters, it is inconvenient to add a lot of add_quote calls from a practical perspective, some string parameters may be missing. As a result, I hope to avoid this problem through some technical means (the biggest problem for C ++ programmers is that they like to create a wheel without doing business ).
Ii. Requirements
1). In operator <(std: ostringstream &, const std: string & msg), a single quotation mark is automatically added to the incoming msg.
2). retain original semantics for other types of Overload
Iii. Solutions
Based on the preceding two points, you only need to overload operator <. For more information, see
Struct SQL _ostream
: Std: wostringstream
{};
SQL _ostream & operator <(SQL _ostream & OS, const std: wstring & msg)
{
Std: wostringstream & tmp = OS;
Tmp <L "'" <msg <L "'";
Return OS;
}
Template <typename T>
SQL _ostream & operator <(SQL _ostream & OS, T msg)
{
Std: wostringstream & tmp = OS;
Tmp <msg;
Return OS;
}
For std: wstring, special processing is performed. For other types of parameters, the base class is called for default processing. Everything looks normal. Is it true? Not necessarily!
When we encounter additional requirements, it will become invalid. Let's take a look at this example:
Struct AA
{
Template <typename CharT>
Operator std: basic_string <CharT> ()
{
Return std: basic_string <CharT> ();
}
};
OS <AA ();
Here, our AA provides implicit conversion (Don't scold me for using this trick, but it is also advantageous) to basic_string. Haha, it's not going to work now. An error is reported during the compilation period. In this implicit conversion, the OS <AA () does not select operator <(SQL _ostream & OS, const std: wstring &), but select the second overload that accepts the template parameters. Why?
Iv. Definition of function Overloading
Here we provide an ADL (Koenig) search.
It is a search rule applied by the compiler when it finds the name of a function call with an unlimited set domain.
First, let's look at the classification of the domain where a function is located:
1: Class domain (a function acts as a member function of a class (static or non-static ))
2: namespace domain
3: full local area
While Koenig searches, the rule is that when the compiler searches for the name of a function call in an infinitely fixed domain, except for the current namespace domain, the namespace of the function parameter type is added to the search range.
ADL is used to ensure that object X of the type can be as simple as using the member function of x (ensure that code that uses an object X of type x can use its nonmember function interface as easily as it can use member functions)
Here is the explanation on wiki. Here is an example of Heber Surte, and here is a CSDN translation.
Here, because of the template function matching problem, we introduce an SFINAE principle (matching error is not a failure ). Of course, there are two rules to remember:
1. Function template specialization does not participate in heavy-load resolutions. The special version of a master template can be used only when it is selected by a heavy-duty decision. In addition, when selecting the main template, the compiler does not care whether it has a specific version.
2. If a common non-template function performs as well as a function template in the Parameter Matching for reload parsing, the compiler selects a common function.
Here is the wiki's explanation of SFINAE.
5. Try again
Now, let's see how to solve the problem. In order to make decisions during the compilation period, we need to introduce an indirect layer and a template component std: enable_if that selects to be overloaded during the compilation period. For more information about std: enable_if, see here. Here, here. Let's take a look at how our code works.
Namespace stdex
{
Typedef std: wostringstream tOstringstream;
Typedef std: wstring tString;
}
Struct SQL _ostream
{
Stdex: tOstringstream OS _;
Stdex: tString str () const
{
Return std: move (OS _. str ());
}
Struct serialize_impl
{
Template <typename T>
Static SQL _ostream & to (SQL _ostream & OS, T msg,
Typename std: enable_if <
Std: is_arithmetic <T>: value |
Std: is_pointer <T>: value
>:: Type * N = 0)
{
Static_assert (std: is_arithmetic <T >:: value | std: is_pointer <T >:: value,
"Must a arithmetic or pointer type ");
OS. OS _ <msg;
Return OS;
}
Static SQL _ostream & to (SQL _ostream & OS, const stdex: tString & msg)
{
OS. OS _ <_ T ("'") <msg <_ T ("'");
Return OS;
}
};
Template <typename T>
SQL _ostream & serialize (T & msg)
{
Return serialize_impl: to (* this, msg );
}
};
Template <typename T>
SQL _ostream & operator <(SQL _ostream & OS, T & msg)
{
OS. serialize (msg );
Return OS;
}
Note the following points:
1). It does not inherit from std: stringstream
2.) template <typename T> SQL _ostream & operator <(SQL _ostream & OS, T & msg) exposes an external interface. The parameter passing method is &&
3). The "to" is overloaded in serialize_impl. The third parameter of the first to function is inferred based on std: enable_if.
For the first point, the inheritance is a piece of money, and when it goes up, it will not be able to get down
For, the interface is simple. Here, it serves as a perfect forwarding function and can be referenced by the right value.
For, I have read the previous link and you should know that the role of enable_if is to eliminate the ambiguity of the template class or template function during heavy load.
In this way, AA can be perfectly converted into to (SQL _ostream & OS, const stdex: tString & msg) through implicit conversion.
Vi. Summary
The overload resolution of C ++ is a very complicated process, especially when it is used in template functions or template classes. To let the compiler know more type information, we introduce std:: enable_if, which is slightly simpler for heavy loads.