1

I have a list of functions that need to be applied to a single string additively. How do I express the "Apply" function.

auto outPutString = inputString
.Apply(Transformation1)
.Apply(Transformation2)

in c++?

The string is the std::string

John Mcdock
  • 1,219
  • 2
  • 11
  • 19
  • To chain member functions, those functions need to return a reference to `*this` or a proxy type that behaves similarly. If `inputString` is an `std::string` then there aren't really any member functions that do this except `operator=` and `assign`. – François Andrieux Jan 31 '19 at 15:10
  • 3
    Is this string your own type or a `std::string`? – NathanOliver Jan 31 '19 at 15:11
  • There once was a somehow similar question which might be of interest: [SO: Completely custom stream operator in c++](https://stackoverflow.com/q/52730391/7478597). – Scheff's Cat Jan 31 '19 at 15:17
  • 1
    I have enjoyed writing code like the above in C#. It is much more natural then the way we have to do it in c++. That said, there is an [open proposal](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4474.pdf) to get it. – NathanOliver Jan 31 '19 at 15:21

7 Answers7

2

From C++ 11 onwards, you may also write an Apply function using variadic templates:

template <typename OutputT, typename InputT>
OutputT Apply(const InputT &obj)
{
    return obj;
}

template <typename OutputT, typename InputT, typename Func, typename... OtherFuncs>
OutputT Apply(const InputT &obj, Func f, OtherFuncs... other)
{
    return Apply<OutputT, decltype(f(obj))>(f(obj), other...);
}

Then you may use this as follows:

auto res = Apply<std::string>(
    "Hello",
    [](const std::string &str) { return str + " "; },    // Applicator 1
    [](const std::string &str) { return str + "World"; } // Applicator 2
);

The result in this case is »Hello World«.

Because the above construction distinguishes between InputT and OutputT, you may "mix" types, as in:

auto res = Apply<size_t>(
    "Hello",
    [](const std::string &str) { return str + " World"; }, // Applicator 1
    [](const std::string &str) { return str.size(); }      // Applicator 2
);

This time the result is 11.

Finally, if you really want to use chaining syntax, you could write a class that wraps the initial object and has an Apply method.

konst
  • 336
  • 2
  • 4
1

Like this:

auto outPutString = Transformation2(Transformation1(inputString));
Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
1
std::string manipulateString(std::string str) {/* do something */; return result;}
std::string manipulateStringAgain(std::string str) {/* do something else */; return result;}

std::string manipulateMe = "hello";

auto resultString = manipulateString(manipulateStringAgain(manipulateMe));
IceFire
  • 4,016
  • 2
  • 31
  • 51
  • 1
    You have the order backwards, which is why the way the OP does it is so much better. `manipulateStringAgain` is actually being called first where with the way you have named it, it should be called last. – NathanOliver Jan 31 '19 at 15:23
  • @NathanOliver you are right, this is a benefit. I have added another answer to address this concern – IceFire Jan 31 '19 at 15:44
1

I'm going to assume when you say "a list of functions", you mean one that varies at runtime. Other answers are better if the list is static.

#include <vector>
#include <string>
#include <functional>
#include <numeric>

std::vector<std::function<std::string(std::string)>> funcs = { Transformation1, Transformation2 }; // or gotten from wherever

auto output = std::accumulate(funcs.begin(), funcs.end(), input, [](auto acc, auto fun){ return fun(acc); });
Caleth
  • 52,200
  • 2
  • 44
  • 75
0

It is possible in C and C++ as well, to define pointer to a fuction and to create vector of pointers to functions. Later, you can invoke functions inside a loop with desired arguments. Please let me know if you are interested for details.

risbo
  • 188
  • 7
  • 1
    Yes, please add details. The OP would like `auto outPutString = inputString .Apply(Transformation1) .Apply(Transformation2)` and I don't see hoe your proposal would get them there. – NathanOliver Jan 31 '19 at 15:22
0

If you would like to keep the order, create some wrapping class and put your manipulation functions in there. For example:

#include <iostream>
#include <string>
using namespace std;

class StringManipulator
{
public:
    StringManipulator(std::string str) : str(str) {}

    operator std::string() {return str;}

    StringManipulator& doSomething() {str += "1"; return *this;}
    StringManipulator& doSomethingElse() {str += "2"; return *this;}

private:
    std::string str;
};

int main() {
    std::string result = StringManipulator("0").doSomething().doSomethingElse();

    std::cout << result;

    return 0;
}

Output is 012.

operator std::string ensures implicit conversion.

IceFire
  • 4,016
  • 2
  • 31
  • 51
0
#include <vector>
#include <iostream>

// three funcitons with a string as the parameter
int funca(std::string& str)
{
   std::cout << "funca:" << str << std::endl;
   return 1; 
}

int funcb(std::string& str)
{
   std::cout << "funcb:" << str << std::endl; 
   return 2;
}

int funcd(std::string& str)
{
  std::cout << "funcd:" << str << std::endl;
  return 3;
}

int main()
{
   // definition of the string
   std::string str = "the string";

   // declare vector of pointers to function returning an int and receiving a     string as a parameter:
   std::vector< int(*)(std::string&)> pf;   

   // load func pointers to vector:
   pf.push_back(&funca); 
   pf.push_back(&funcb);
   pf.push_back(&funcd);

   //declare vector iterator:
   std::vector<int (*)(std::string&)>::iterator it;

   // iterate vector of func pointers:
   for (it = pf.begin() ; it != pf.end(); ++it)
   {
      // function call using pointers and passing parameter str
      // you can get return value as from 'normal' function 
      int ret = (*it)(str);
      std::cout << "function returns:" << ret << std::endl; 
   }
}

/*
compiled and executed on ubuntu 18.04, output:
funca:the string
function returns:1
funcb:the string
function returns:2
funcd:the string
function returns:3
*/
risbo
  • 188
  • 7