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;
}