How can I combine in-place transformation and copy transformation?

I would like to combine these two functions into one functional interface:

T& Transform(T & foo){
   //transform t
   return t;
}

T As_Transformed(T foo){
   //transform t
   return t;
}

Sometimes I want to convert a variable passed to a function.
In other cases, I want a new variable with the conversion applied.

As a result, I end up creating two functions every time and following my agreement, where I As_take and return a copy, while I As_don’t accept or return a link.

How can I write an implementation of one function that will handle both of these behaviors?

I have no requirements for how this should look, but I would like for me not to rely on my agreement As_, and ideally, where I do only one function instead of two.


:
, . Uppercase() As_Upercased()

std::string str1 = "hello";
Uppercase(str1); //str is now "HELLO"

std::string str2 = "hello";
auto str3 = As_Uppercased(str2); //str2 is still "hello", 
                                 //but str3 is "HELLO"

, , :

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"

, , - .
- , .

+4
6

, , std::reference_wrapper. :

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

:

original copy copy
original
original ref ref
original ref ref

, .

, by-by-copy, std::ref, .

LIVE

+7

, , . , , . , . .

, .

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

:

T t;
auto copy = copy_and_call(t, Transform);

, , , , , , . Transform , . .

T t = "original"; // let say T can be constructed from a string literal
T copy = t;
Transform(copy);
+3

:

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

, , , :

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);
                       ^

, , 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";

}

:

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<--
+3

- ( ) , . , , . , .

// 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); });
0

, ++ .

, Transform - . - , . - , , .

:

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

Edit: I got inspiration from the specification std::asyncwhere, if you go back to the returned one std::future, then the method behaves wisely. But if you call std::asyncdirectly, it becomes a lock.

0
source

Result of output by function parameter and return

T Transform(T &foo, T* output = 0){
    // transform t
    output = &t;
    return t;
}
-1
source

Source: https://habr.com/ru/post/1617099/


All Articles