我喜欢使用Haskell,但我被迫使用C ++进行学校作业。我正在为C ++编写自己的库来模拟Haskell的Prelude函数,所以如果我愿意的话,我可以用C ++编写一个更简洁和功能更强的样式(repo on GitHub)。
我遇到的一个问题是实现对列表进行操作的map
等函数。在Haskell中,String
等同于[Char]
,因此您可以在采用列表的函数中使用字符串。在C ++中,std::string
不与std::vector<char>
相同,因此我必须编写多个版本的函数来执行std::string
或std::vector<Type>
。这适用于filter
或tail
等函数,因为它们的输入和输出是相同的类型。但是对于map
,我需要能够将int
s的向量转换为char
s,或将string
转换为bool
s的向量。
尝试运行简单的猪拉丁转换器(pigLatin.cpp
on GitHub)时,unwords
函数失败,因为map
无效。
examples/pigLatin.cpp:20:29: error: no matching function for call to 'unwords'
std::string translation = unwords(map(pigLatin, words(input)));
^~~~~~~
examples/../prelude.hpp:591:15: note: candidate function not viable: no known conversion from 'std::string' (aka 'basic_string<char, char_traits<char>, allocator<char> >') to 'const std::vector<std::string>'
(aka 'const vector<basic_string<char, char_traits<char>, allocator<char> > >') for 1st argument
std::string unwords(const std::vector<std::string> &xs) {
^
1 error generated.
如何编写map
函数,使其行为类似于Haskell(map
on Hackage)中的函数:
map :: (a -> b) -> [a] -> [b]
我不太了解C ++模板的细微差别来解决这个问题。这是我到目前为止(map
from prelude.hpp
on GitHub):
// map :: (a -> b) -> [a] -> [b]
template <typename Function, typename Input, typename Output>
std::vector<Output> map(const Function &f, const std::vector<Input> &xs) {
const int size = xs.size();
std::vector<Output> temp;
for (int i = 0; i < size; ++i) {
temp.push_back(f(xs[i]));
}
return temp;
}
// map :: (String -> a) -> String -> [a]
template <typename Function, typename Output>
std::vector<Output> map(const Function &f, const std::string &xs) {
const int size = xs.size();
std::vector<Output> temp;
for (int i = 0; i < size; ++i) {
temp.push_back(f(xs[i]));
}
return temp;
}
// map :: (a -> String) -> [a] -> String
template <typename Function, typename Input>
std::string map(const Function &f, const std::vector<Input> &xs) {
const int size = xs.size();
std::string temp;
for (int i = 0; i < size; ++i) {
temp += f(xs[i]);
}
return temp;
}
// map :: (String -> String) -> String -> String
template <typename Function>
std::string map(const Function &f, const std::string &xs) {
const int size = xs.size();
std::string temp;
for (int i = 0; i < size; ++i) {
temp += f(xs[i]);
}
return temp;
}
答案 0 :(得分:8)
在此声明中:
template <typename Function, typename Input, typename Output>
std::vector<Output> map(const Function &f, const std::vector<Input> &xs);
Output
是一个非推断的上下文。编译器将从提供的参数中推导出Function
和Input
的类型,但不能推导出Output
- 必须明确提供它。那不会发生。
您要做的是自己计算Output
与Function
和Input
的函数关系。使用C ++ 17编译器/库,即std::invoke_result_t
(在C ++ 11上,使用result_of
)。那就是:
template <typename Function, typename Input,
typename Output = std::invoke_result_t<Function const&, Input const&>>
std::vector<Output> map(const Function &f, const std::vector<Input> &xs);
这是一个默认的模板参数,但由于用户实际上不会提供它,因此将使用默认参数,这就是您想要的。现在这也不完全正确,因为invoke_result_t
可以返回一些你不能放在vector
中的东西(比如参考)。所以我们需要std::decay
它。此外,您需要保留输出向量,因为我们事先知道它的大小:
template <typename Function, typename Input,
typename Output = std::decay_t<std::invoke_result_t<Function&, Input const&>>>
std::vector<Output> map(Function&& f, const std::vector<Input> &xs)
{
std::vector<Output> res;
res.reserve(xs.size());
for (auto&& elem : xs) {
res.push_back(f(elem));
}
return res;
}
现在,如果您希望这能够获得string
并返回vector<X>
或一个string
,那么现在变得相当复杂。你不能在C ++中重载返回类型,所以提供这两个重载是不正确的。它恰好适用于您的情况,因为string --> vector<X>
重载将因X
无法推断而被删除。但是一旦你解决了这个问题,你就会遇到这个问题。