Преобразовать n бинарных вызовов в один n-арный вызов в C ++? - PullRequest
7 голосов
/ 18 мая 2011

У нас есть вспомогательная функция в нашей кодовой базе для объединения двух (Windows) строк пути:

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

Часто используется следующим образом:

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

Это довольно приемлемо, но меня удивило, есть ли какая-то возможность в C ++ (или C ++ 0x) использовать функцию (template?) Для объединения вызовов двоичных функций.

То есть при наличии функции 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);

Пожалуйста, этот вопрос о преобразовании, а , а не о наилучшем способе обработки или объединения строк пути!


Редактировать: Спасибо deft_code answer за предоставление правильного термина для того, что я просил: Fold (функция более высокого порядка) ) . (Обратите внимание, что я согласился принять ответ Матье , потому что его решение не требует C ++ 0x.)

Ответы [ 5 ]

13 голосов
/ 18 мая 2011

Без C ++ 0x также возможно использование цепочки (я не рекомендую перегружать оператор запятой, синтаксис становится странным).

Синтаксис несколько иной, но очень близкий:

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

Это делается просто путем создания временного объекта, который будет выполнять сцепление через перегрузку operator() и который будет неявно преобразован через 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;
};

Затем вы настраиваете AppendPath, чтобы вернуть такой объект:

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

(Обратите внимание, на самом деле вы можете прямо назвать его AppendPath)

Обобщение в соответствии с предложением Мартина:

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

10 голосов
/ 18 мая 2011

В 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);
}
6 голосов
/ 18 мая 2011

Создание Решение Мартиньо Фернандеса более обобщенно:

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

Конечно, вы можете добавить эту перегрузку и прозрачно использовать ее в своем коде.


Или передать список инициализаторов:

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-подобным контейнером. Вы также можете добавить эти перегрузки в свой код и использовать их прозрачно.

2 голосов
/ 18 мая 2011

В этом конкретном случае вызова 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);
}

Что, я уверен, может быть легко сгенерировано автоматически.

1 голос
/ 20 мая 2011

Учитывая функцию T f (T arg1, T arg2), можно ли написать функцию T ncall (FnT fn, T arg1, T arg2, T arg3, ...), которая будет вызывать f, как в моем примере выше и вернуть результат?

Все очень близки к обычной реализации. Вот еще более общее решение, чем заданный вопрос. Он принимает такие функции, как 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 );

Другой пример, демонстрирующий всю силу фолдла:

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 .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...