开发者

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 Append开发者_运维百科Path(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);
}


Making Martinho Fernandes' solution more generic:

#define AUTO_RETURN(EXPR) -> decltype(EXPR) \
{ return EXPR; }

template<class F, class Arg1, class ...Args>
auto n_binary_to_1_nary(F func, Arg1 &&a, Args &&...rest)
AUTO_RETURN(func(std::forward<Arg1>(a),
                 n_binary_to_1_nary(func, std::forward<Args>(rest)...))))

template<class F, class Arg1, class Arg2>
auto n_binary_to_1_nary(F func, Arg1 &&a, Arg2 &&b)
AUTO_RETURN(func(std::forward<Arg1>(a), std::forward<Arg2>(b)))

Use:

n_binary_to_1_nary(&AppendPath, base, toplevel, sub1, filename)

However, AppendPath could simply be written in this style:

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

template<class ...Args>
CString AppendPath(CString const &a, CString const &b, Args const &...rest) {
  return AppendPath(AppendPath(a, b), rest...);
}

You can, of course, add this overload and use it transparently in your code.


Or pass an initializer_list:

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

Code:

template<class Iter>
CString AppendPath(Iter begin, Iter end) {
  CString result;
  if (begin == end) {
    assert(!"reporting an error (however you like) on an empty list of paths"
            " is probably a good idea");
  }
  else {
    result = *begin;
    while (++begin != end) {
      result = AppendPath(result, *begin);
    }
  }
  return result;
}

template<class C>
CString AppendPath(C const &c) {
  return AppendPath(c.begin(), c.end());
}

Notice the last AppendPath works with any STL-like container. You can also add these overloads to your code and use them transparently.


In this specific case of calling AppendPath I'd just write an overload of the function which has as its implementation your second line of code.

In the general case I'd write a series of templates:

template<typename T>
T ncall(T (*fn)(T const&,T const&), T const& p1, T const& p2, T const& p3){
    return fn(fn(p1, p2), p3);
}
template<typename T>
T ncall(T (*fn)(T const&,T const&), T const& p1, T const& p2, T const& p3, T const& p4){
    return ncall(fn, fn(p1, p2), p3, p4);
}
template<typename T>
T ncall(T (*fn)(T const&,T const&), T const& p1, T const& p2, T const& p3, T const& p4, T const& p5){
    return ncall(fn, fn(p1, p2), p3, p4, p5);
}

Which I'm sure could be easily generated automatically.


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?

Everyone is soo close to a general foldl implementation. Here is an even more general solution than the question asked. It accepts functions like T f(T arg1, T arg2) as well as T1 f(T2 arg1, T3 arg2). Also I named the function foldl in homage to its functional roots.

#define AUTO_RETURN( EXPR ) -> decltype( EXPR ) \
{ return EXPR; }

template< typename BinaryFunc, typename First, typename Second >
auto foldl( BinaryFunc&& func, First&& first, Second&& second )
AUTO_RETURN( func( std::forward<First>(first), std::forward<Second>(second) ) )

template<typename BinaryFunc,typename First, typename Second, typename... Rest >
auto foldl( BinaryFunc&& func, First&& first, Second&& second, Rest&&... rest )
AUTO_RETURN(
   foldl(
      std::forward<BinaryFunc>(func),
      std::forward<decltype( func(first,second) )>(
         func( std::forward<First>(first), std::forward<Second>(second) )),
      std::forward<Rest>(rest)... )
   )

An example of how this would solve your problem:

auto path = foldl( &AppendPath, base, toplevel, sub1, filename );

Another example showing off all of foldl's strength:

struct stream
{
   template< typename T >
   std::ostream& operator()( std::ostream& out, T&& t ) const
   {
      out << std::forward<T>(t);
      return out;
   }
};

struct Foo
{
   Foo( void ) = default;
   Foo( const Foo& ) = delete;
   Foo& operator=( const Foo& ) = delete;
};

std::ostream& operator << ( std::ostream& out, const Foo& )
{
   out << "foo";
   return out;
}

int main()
{
   foldl( stream(), std::cout, 1, ' ', 1.1, ' ', Foo{}, '\n' );
}

See the output / code in action at ideone.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜