我们的代码库中有一个辅助函数来连接两个(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);
请问,这个问题是关于转换和不关于处理或连接路径字符串的最佳方法!
修改:感谢deft_code的answer提供了我所要求的正确用语:Fold (higher-order function)。 (请注意,我已经决定接受the answer of Matthieu,因为他的解决方案不需要C ++ 0x。)
答案 0 :(得分:13)
没有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
)
根据@ 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上验证了代码。
答案 1 :(得分:10)
在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);
}
答案 2 :(得分:6)
使Martinho Fernandes' solution更通用:
#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的容器。您还可以将这些重载添加到代码中并透明地使用它们。
答案 3 :(得分:2)
在这个调用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);
}
我确信可以很容易地自动生成。
答案 4 :(得分:1)
给定一个函数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处的输出/代码。