C++ 如何组合就地转换和副本转换?
我想将这两个功能组合成一个单一功能接口:C++ 如何组合就地转换和副本转换?,c++,function,templates,overloading,c++17,C++,Function,Templates,Overloading,C++17,我想将这两个功能组合成一个单一功能接口: T& Transform(T & foo){ //transform t return t; } T As_Transformed(T foo){ //transform t return t; } 有时我想转换传递到函数中的变量。 其他时候,我需要一个新的变量和应用的转换 结果,我每次都会创建两个函数,并遵循我的一个虚构约定,即As\uu获取并返回一个副本,而不是As\uu获取并返回一个引用 如何编写处理这两
T& Transform(T & foo){
//transform t
return t;
}
T As_Transformed(T foo){
//transform t
return t;
}
有时我想转换传递到函数中的变量。其他时候,我需要一个新的变量和应用的转换 结果,我每次都会创建两个函数,并遵循我的一个虚构约定,即
As\uu
获取并返回一个副本,而不是As\uu
获取并返回一个引用
如何编写处理这两种行为的单个函数实现
我对它应该是什么样子没有任何要求,但我希望有一种方式,我不依赖于我的As_uu
约定,理想情况下,我只做一个函数,而不是两个
示例:
下面是一个这样的示例。
让我们把
Uppercase()
和当作大写的(
我不知道组合界面会是什么样子,但也许:
std::string str1 = "hello";
Uppercase<REF>(str1); //str is now "HELLO"
std::string str2 = "hello";
auto str3 = Uppercase<COPY>(str2); //str2 is still "hello",
//but str3 is "HELLO"
std::string str1=“你好”;
大写(str1)//str现在是“你好”
std::string str2=“你好”;
自动str3=大写(str2)//str2仍然是“你好”,
//但是str3是“你好”
或者我可以用参考包装做点什么。我要问的是这个函数的可能实现。通过函数param和return输出结果
T Transform(T &foo, T* output = 0){
// transform t
output = &t;
return t;
}
一种明显的可能性是为转换的源和目标传递迭代器(或范围)。如果为源和目标传递相同的范围,它将在适当的位置进行变换。如果传递单独的源和目标,则目标与源是分开的
// copy transformation
std::transform(in.begin(), in.end(), std::back_inserter(out),
[](auto x) { return transformed(x); });
// in-place transformation:
std::transform(in.begin(), in.end(), in.begin(),
[](auto x) { return transformed(x); });
使用范围,您可以获得相同的基本效果,但语法开销要小得多,因此它们看起来像:
// copy transformation
transform(in, result, [](auto x) { return transformed(x); });
// in-place:
transform(in, in, [](auto x) { return transformed(x); });
不,你不能合并它们,因为它们做的事情不同。一个修改传递给函数的对象,另一个保持原始对象不变。一个复制对象并返回它,另一个返回对原始对象的引用。这些都是相互矛盾的要求 但是,一个可以使用另一个来实现,这样就不会有逻辑上的重复
T As_Transformed(T t){
Transform(t);
return t;
}
上述内容对于类似于Transform
的任何其他函数都是相同的,因此,如果您希望避免复制样板文件,可以将其概括为模板,并使用该模板:
template<class T, class Func>
auto copy_and_call(T copy, Func&& function) {
function(copy);
return copy;
}
同样,如果您需要两种形式的函数的数量很高,但函数调用的数量很低,那么这可能不保证声明一个函数。您可以简单地在副本上使用Transform
,而不是在函数中使用。下面的代码并不多
T t = "original"; // let's say T can be constructed from a string literal
T copy = t;
Transform(copy);
您可以通过在
std::reference\u包装器上提供重载来告诉编译器如何区分这两种类型。然后代码将如下所示,例如:
#include <iostream>
#include <functional>
using T = std::string;
T Transform(T t)
{
t += " copy";
return t;
}
std::reference_wrapper<T> Transform(std::reference_wrapper<T> t)
{
t.get() += " ref";
return t;
}
int main()
{
T t{"original"};
std::cout << Transform(Transform(t)) << "\n";
std::cout << t << "\n";
std::cout << Transform(Transform(std::ref(t))).get() << "\n";
std::cout << t;
}
请注意,原始值在第一个调用链之后保持不变,在第二个调用链之后进行修改
在您的实际代码中,第一个重载将调用第二个重载来转换使用std::ref
包装的passed by copy参数,以避免代码重复
我建议使用基于名称空间的替代方案:
#include <string>
#include <algorithm>
namespace inplace {
std::string& lowercase(std::string& src) {
std::transform(src.begin(), src.end(), src.begin(), ::tolower);
return src;
}
} // namespace inplace
std::string lowercase(std::string src) {
return inplace::lowercase(src);
}
请注意,用户不可能发出指令
using namespace inplace;
因为这会导致歧义;这是编译错误的一部分:
error: call of overloaded 'lowercase(std::__cxx11::string&)' is ambiguous
auto c = lowercase(b);
^
因此,编译器将确保您必须使用inplace
函数的完全限定名
下面的代码显示了字符串突变及其复制副本的集合
#include <iostream>
#include <string>
#include <algorithm>
namespace inplace {
bool is_not_space(char c) {
return not std::isspace(c);
}
inline std::string& uppercase(std::string& src) {
std::transform(src.begin(), src.end(), src.begin(), ::toupper);
return src;
}
inline std::string& lowercase(std::string& src) {
std::transform(src.begin(), src.end(), src.begin(), ::tolower);
return src;
}
// Credit for the idea of ltrim, rtrim, and trim goes to Stackoverflow
// user Evan Teran: http://stackoverflow.com/users/13430/evan-teran
inline std::string& ltrim(std::string& src) {
src.erase(src.begin(), std::find_if(src.begin(), src.end(), is_not_space));
return src;
}
inline std::string& rtrim(std::string& src) {
src.erase(std::find_if(src.rbegin(), src.rend(), is_not_space).base(), src.end());
return src;
}
inline std::string& trim(std::string& src) {
return ltrim(rtrim(src));
}
inline std::string& normalize(std::string& src) {
return lowercase(trim(src));
}
}
// The create-a-copy versions simply forward the call to the in-place
// versions after having taken their argument by value.
inline std::string lowercase(std::string src) { return inplace::lowercase(src); }
inline std::string uppercase(std::string src) { return inplace::uppercase(src); }
inline std::string ltrim(std::string src) { return inplace::ltrim(src); }
inline std::string rtrim(std::string src) { return inplace::rtrim(src); }
inline std::string trim(std::string src) { return inplace::trim(src); }
inline std::string normalize(std::string src) { return inplace::normalize(src); }
int main() {
std::string a = "ThIs Is A sTrInG";
std::string b = "ThIs Is AnOtHeR sTrInG";
// it is clear that I intend to lowercase-in-place
inplace::lowercase(a);
// I can document that, in the absence of namespace, all mutations
// create a copy
auto c = lowercase(b);
std::cout << "a: " << a << "\n"
<< "b: " << b << "\n"
<< "c: " << c << "\n";
std::string d = " I NEED to normaliZE ThIs StrINg\r\n\t\t ";
std::string e = "\t\t\n\rAnD THIS one Too \n\t\t ";
// again: transparent that I will do this in-place
inplace::normalize(d);
auto f = normalize(e);
std::cout << "-->" << d << "<--\n"
<< "-->" << e << "<--\n"
<< "-->" << f << "<--\n";
}
#包括
#包括
#包括
原地命名空间{
bool是\u not u空间(char c){
返回非标准::isspace(c);
}
内联std::string和大写(std::string和src){
std::transform(src.begin(),src.end(),src.begin(),::toupper);
返回src;
}
内联std::string和小写(std::string和src){
std::transform(src.begin(),src.end(),src.begin(),::tolower);
返回src;
}
//ltrim、rtrim和trim的理念归功于Stackoverflow
//用户Evan Teran:http://stackoverflow.com/users/13430/evan-teran
内联std::string和ltrim(std::string和src){
src.erase(src.begin(),std::find_if(src.begin(),src.end(),is_not_space));
返回src;
}
内联std::string和rtrim(std::string和src){
src.erase(std::find_if(src.rbegin(),src.rend(),is_not_space).base(),src.end());
返回src;
}
内联std::string和trim(std::string和src){
返回ltrim(rtrim(src));
}
内联std::string和normalize(std::string和src){
返回小写(trim(src));
}
}
//create-a-copy版本只需将调用转发到原位
//按值获取参数后的版本。
内联std::string lowercase(std::string src){return inplace::lowercase(src);}
内联std::string大写(std::string src){return inplace::uppercase(src);}
内联std::string ltrim(std::string src){return inplace::ltrim(src);}
内联std::string rtrim(std::string src){return inplace::rtrim(src);}
内联std::string trim(std::string src){return inplace::trim(src);}
内联std::string normalize(std::string src){return inplace::normalize(src);}
int main(){
std::string a=“这是一个字符串”;
std::string b=“这是另一个字符串”;
//很明显,我打算使用小写字母
小写(a);
//在没有名称空间的情况下,我可以记录所有的突变
//创建副本
自动c=小写(b);
STD::CUT< P>我完全同意Rostislav的解决方案,我无法抗拒提出C++标准的方式来解决这个问题的诱惑。
其思想是,您的Transform
方法可以返回一个代理对象来延迟转换的执行,而不是应用转换i
int main() {
std::string a = "ThIs Is A sTrInG";
std::string b = "ThIs Is AnOtHeR sTrInG";
// it is clear that I intend to lowercase-in-place
inplace::lowercase(a);
// I can document that, in the absence of namespace, all mutations
// create a copy
auto c = lowercase(b);
std::cout << "a: " << a << "\n"
<< "b: " << b << "\n"
<< "c: " << c << "\n";
}
a: this is a string
b: ThIs Is AnOtHeR sTrInG
c: this is another string
using namespace inplace;
error: call of overloaded 'lowercase(std::__cxx11::string&)' is ambiguous
auto c = lowercase(b);
^
#include <iostream>
#include <string>
#include <algorithm>
namespace inplace {
bool is_not_space(char c) {
return not std::isspace(c);
}
inline std::string& uppercase(std::string& src) {
std::transform(src.begin(), src.end(), src.begin(), ::toupper);
return src;
}
inline std::string& lowercase(std::string& src) {
std::transform(src.begin(), src.end(), src.begin(), ::tolower);
return src;
}
// Credit for the idea of ltrim, rtrim, and trim goes to Stackoverflow
// user Evan Teran: http://stackoverflow.com/users/13430/evan-teran
inline std::string& ltrim(std::string& src) {
src.erase(src.begin(), std::find_if(src.begin(), src.end(), is_not_space));
return src;
}
inline std::string& rtrim(std::string& src) {
src.erase(std::find_if(src.rbegin(), src.rend(), is_not_space).base(), src.end());
return src;
}
inline std::string& trim(std::string& src) {
return ltrim(rtrim(src));
}
inline std::string& normalize(std::string& src) {
return lowercase(trim(src));
}
}
// The create-a-copy versions simply forward the call to the in-place
// versions after having taken their argument by value.
inline std::string lowercase(std::string src) { return inplace::lowercase(src); }
inline std::string uppercase(std::string src) { return inplace::uppercase(src); }
inline std::string ltrim(std::string src) { return inplace::ltrim(src); }
inline std::string rtrim(std::string src) { return inplace::rtrim(src); }
inline std::string trim(std::string src) { return inplace::trim(src); }
inline std::string normalize(std::string src) { return inplace::normalize(src); }
int main() {
std::string a = "ThIs Is A sTrInG";
std::string b = "ThIs Is AnOtHeR sTrInG";
// it is clear that I intend to lowercase-in-place
inplace::lowercase(a);
// I can document that, in the absence of namespace, all mutations
// create a copy
auto c = lowercase(b);
std::cout << "a: " << a << "\n"
<< "b: " << b << "\n"
<< "c: " << c << "\n";
std::string d = " I NEED to normaliZE ThIs StrINg\r\n\t\t ";
std::string e = "\t\t\n\rAnD THIS one Too \n\t\t ";
// again: transparent that I will do this in-place
inplace::normalize(d);
auto f = normalize(e);
std::cout << "-->" << d << "<--\n"
<< "-->" << e << "<--\n"
<< "-->" << f << "<--\n";
}
a: this is a string
b: ThIs Is AnOtHeR sTrInG
c: this is another string
-->i need to normalize this string<--
-->
AnD THIS one Too
<--
-->and this one too<--
struct Proxy {
OriginalObject& ref;
bool copied;
operator OriginalObject() {
OriginalObject copy(ref);
transform copy;
copied := true;
return copy;
}
~Proxy() {
if (not copied)
transform original reference
}
};
Proxy Transform(OriginalObject& obj) { ... }
OriginalObject copy = Transform(myRef); // proxy gets copied
// therefore transformed the copy
Transform(myRef);// proxy gets destructed, therefore transformed the original
///////////SPOILER ALERT/////////////////
{
auto cynicCopy = Tranform(myRef);//proxy copied as proxy
(myRef);//original is retained until proxy gets destroyed
} // proxy gets destroyed, original modified :)
#include <iostream>
#include <string>
template<typename T, typename Operation>
//Proxy class for transformations
struct CynicTransform {
std::reference_wrapper<T> ref;
Operation op;
bool opDtor;
CynicTransform(T& r, Operation o)
:
ref(r),
op(o),
opDtor(true)
{ }
CynicTransform(const CynicTransform&) = delete;
CynicTransform& operator=(const CynicTransform&) = delete;
CynicTransform(CynicTransform&& ct)
:
ref(ct.ref),
op(ct.op),
opDtor(ct.opDtor)
{
ct.opDtor = false;
}
CynicTransform& operator=(CynicTransform&& ct) {
using std::swap;
swap(ref, ct.ref);
swap(op, ct.op);
swap(opDtor, ct.opDtor);
return *this;
}
~CynicTransform() {
if (opDtor) {
op(ref.get());
}
}
operator T() {
T copy(ref.get());
op(copy);
opDtor = false;
return copy;
}
};
template<typename T, typename Operation>
//Provides ease of use for the proxy class
CynicTransform<T, Operation> MakeCynicTransform(T& tref, Operation op)
{
return CynicTransform<T, Operation>(tref, op);
}
/*example implementation of a transformation method
* each of your transformation method may look like this
*/
auto Transform(std::string& strRef)
{
return MakeCynicTransform(strRef, [](std::string& strRef) {
/*modification logic here*/
strRef += " modified";
});
}
int main() {
std::string s("original");
std::cout << "Initially: " << s << std::endl;
std::string copy = Transform(s);
std::cout << "Original: " << s << std::endl;
std::cout << "Copy: " << copy << std::endl;
{
auto cynicCopy = Transform(s);
std::cout << "Scope Original: " << s << std::endl;
}
std::cout << "Cynic Modified: " << s << std::endl;
Transform(s);
std::cout << "At Last: " << s << std::endl;
}