将 n 个二进制调用转换为 C++ 中的一个 n 元调用?

发布于 2024-11-08 14:26:16 字数 1260 浏览 0 评论 0原文

我们的代码库中有一个辅助函数来连接两个(Windows)路径字符串:

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

它经常以这种方式使用:

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

这是相当可以接受的,但它让我想知道 C++(或 C++0x)中是否有可能使用(模板?)函数将二进制函数调用链接在一起。

也就是说,给定一个函数 T f(T arg1, T arg2) 是否可以编写函数 T ncall(FnT fn, T arg1, T arg2, T arg3, ...) 会像上面的示例一样调用 f 并返回结果?

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

拜托,这个问题是关于转换的,不是< /strong> 关于处理或连接路径字符串的最佳方法!


编辑: 感谢 deft_code回答为我所要求的内容提供正确的术语:折叠(高阶函数)(请注意,我已决定接受 Matthieu 的答案,因为他的解决方案不需要 C++0x。)

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.)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

断舍离 2024-11-15 14:26:16

如果没有 C++0x,也可以使用链接(我不建议重载逗号运算符,语法会变得很奇怪)。

语法有些不同,但非常接近:

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

只需创建一个临时对象即可完成,该对象将通过 operator() 的重载执行串联,并且可通过 operator CString 隐式转换() 常量

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

然后,您调整 AppendPath 以返回这样的对象:(

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

注意,实际上您可以直接将其命名为 AppendPath

按照 @Martin 的建议使其通用:

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

ideone

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.

烟雨扶苏 2024-11-15 14:26:16

在 C++0x 中,您可以使用可变参数模板。也许是这样的:

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

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);
}
念﹏祤嫣 2024-11-15 14:26:16

制作Martinho Fernandes的解决方案< /a> 更通用:

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

使用:

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

但是,AppendPath 可以简单地以这种风格编写:

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

当然,您可以添加此重载并在代码中透明地使用它。


或者传递一个initializer_list:

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

代码:

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

注意最后一个AppendPath 适用于任何类似STL 的容器。您还可以将这些重载添加到代码中并透明地使用它们。

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.

将军与妓 2024-11-15 14:26:16

在调用 AppendPath 的这种特定情况下,我只需编写函数的重载,该函数的实现就是您的第二行代码。

在一般情况下,我会编写一系列模板:

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

我确信可以轻松自动生成这些模板。

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.

┼── 2024-11-15 14:26:16

给定一个函数 T f(T arg1, T arg2) 是否可以编写一个函数 T ncall(FnT fn, T arg1, T arg2, T arg3, ...) 来调用 f,就像上面的例子一样并返回结果?

每个人都非常接近通用的foldl 实现。这是一个比所提出的问题更通用的解决方案。它接受诸如 T f(T arg1, T arg2) 以及 T1 f(T2 arg1, T3 arg2) 之类的函数。此外,我将函数命名为 foldl 以向其函数根源致敬。

#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)... )
   )

这是如何解决您的问题的示例:

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

另一个展示foldl所有优势的示例:

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

请参阅 ideone< 的实际输出/代码/a>.

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文