Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Transforming n binary calls to one n-ary call in C++?

We have a helper function in our codebase to concatenate two (Windows) path strings:

CString AppendPath(CString const& part1, CString const& part2);

It is often used in this way:

const CString filePath = AppendPath(AppendPath(AppendPath(base, toplevel), sub1), filename);

This is rather acceptable, but it got me wondering if there is some possibility in C++ (or C++0x) to use a (template?) function to chain binary function calls together.

That is, given a function T f(T arg1, T arg2) is it possible to write a function T ncall(FnT fn, T arg1, T arg2, T arg3, ...) that will call f like in my example above and return the result?

// could roughly look like this with my example:
const CString filePath = ncall(&AppendPath, base, toplevel, sub1, filename);

Please, this question is about the transformation and not about the best way to handle or concatenate path strings!


Edit: Thanks to deft_code's answer for providing the correct term for what I was asking for: Fold (higher-order function). (Note that I have settled on accepting the answer of Matthieu because his solution does not require C++0x.)

like image 294
Martin Ba Avatar asked May 18 '11 08:05

Martin Ba


2 Answers

Without C++0x, it's also possible to use chaining (I don't recommend overloading the comma operator, the syntax gets weird).

The syntax is somewhat different, but very close:

CString const Path = AppendPath(base)(toplevel)(sub1)(filename);

This is done simply by creating a temporary object that will perform the catenation through an overload of operator() and which will be implicitly convertible through operator CString() const.

class AppenderPath
{
public:
  AppenderPath(){}
  AppenderPath(CString s): _stream(s) {}

  AppenderPath& operator()(CString const& rhs) {
    _stream += "/";
    _stream += rhs;
    return *this;
  }

  operator CString() const { return _stream; }

private:
  CString _stream;
};

Then, you tweak AppendPath to return such an object:

AppenderPath AppendPath(CString s) { return AppenderPath(s); }

(Note, actually you could directly name it AppendPath)

Making it generic as per @Martin's suggestion:

#include <iostream>
#include <string>

template <typename L, typename R>
class Fold1l
{
public:
  typedef void (*Func)(L&, R const&);

  Fold1l(Func func, L l): _func(func), _acc(l) {}

  Fold1l& operator()(R const& r) { (*_func)(_acc, r); return *this; }

  operator L() const { return _acc; }

private:
  Func _func;
  L _acc;
};

// U is just to foil argument deduction issue,
// since we only want U to be convertible into a R
template <typename R, typename L, typename U>
Fold1l<R,L> fold1l(void (*func)(L&, R const&), U l) {
  return Fold1l<R,L>(func, l);
}

void AppendPath(std::string& path, std::string const& next) {
  path += "/"; path += next;
}

int main() {
  std::string const path = fold1l(AppendPath, "base")("next");
  std::cout << path << std::endl;
}

Code validated on ideone.

like image 71
Matthieu M. Avatar answered Nov 17 '22 23:11

Matthieu M.


In C++0x, you can use variadic templates. Something like this, perhaps:

template<typename... Args>
CString AppendAllPaths(CString const& part1, Args const&... partn)
{
    return AppendPath(part1, AppendAllPaths(partn...));
}

template<>
CString AppendAllPaths(CString const& part1, CString const& part2)
{
    return AppendPath(part1, part2);
}
like image 10
R. Martinho Fernandes Avatar answered Nov 18 '22 00:11

R. Martinho Fernandes