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.)
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.
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);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With