我来自Haskell,目前正在修补C ++ 11以了解它能做些什么。我的一个玩具是一个小模板,它试图模仿Haskell map
函数,即它需要一个容量为X
的容器和一个将X
映射到Y
的函数。并生成一个值为Y
的容器。我知道我可以使用std::transform
轻松做到这一点,但这会破坏乐趣。
现在,我的模板看起来像这样:
template <typename T, typename U>
void myMap( const T &input,
U &output,
std::function<typename U::value_type (typename T::value_type)> f );
现在,我的qustion是:是否可以调整签名,以便不通过引用(第二个参数)获取输出容器,而是通过返回值生成一个新容器,但编译器可以推导出返回类型?像
这样的东西template <typename T, typename U>
U myMap( const T &input,
std::function<typename U::value_type (typename T::value_type)> f );
遗憾的是不能像
一样调用std::vector<int> x = { 1, 2, 3, 4 };
std::list<bool> y = myMap( x, []( int x ) { return x % 2 == 0; } );
......至少Clang在这里没有推断出返回类型。
我的一个想法是,鉴于输入容器类型和函数类型是已知的,您可以从中构造输出类型。即
之类的东西template <typename C, typename T, typename U>
C<U> myMap( const C<T> &input,
std::function<U (T)> f );
...但是唉C<U>
似乎不是有效的语法。我想知道我是否只需要像this question那样的decltype
仙尘。{/ p>
答案 0 :(得分:8)
正如我之前所说过的,我之前已经完成了所有操作,但由于使用了std::basic_string<T,U>
而无法与std::set
(以及std::back_inserter
和朋友)合作,因此它只是将它重新绑定到std::basic_string<stuff,U>
而不是基础容器。但请注意,将其扩展为使用std::basic_string<T, U>
的专门案例很容易。
我做的第一件事是定义一个function_traits
和一个Rebind
元函数,它会将类型从Container<T>
重新绑定到Container<U>
,其中U
是结果类型传递的函数和T
是原始类型。结果类型可通过function_traits
元函数找到。您可以在下面看到完整的代码:
#include <type_traits>
#include <algorithm>
/* Helpers */
template<typename T>
using Type = typename T::type;
template<typename T>
using Unqualified = Type<std::remove_reference<Type<std::remove_cv<T>>>>;
template<typename Specialization, typename Target>
struct rebind {};
/* Sensible default: assume first parameter is for the target */
template<template<typename...> class Cont, typename T, typename... Ts, typename Target>
struct rebind<Cont<T, Ts...>, Target> {
using type = Cont<Target, Ts...>;
};
/* Special-case */
template<typename Old, std::size_t N, typename Target>
struct rebind<std::array<Old, N>, Target> {
using type = std::array<Target, N>;
};
template<typename Specialization, typename Target>
using Rebind = Type<rebind<Specialization, Target>>;
#include <tuple>
template<typename T>
struct function_traits : public function_traits<decltype(&T::operator())> {};
template<typename T, typename R, typename... Args>
struct function_traits<R(T::*)(Args...) const> {
static constexpr size_t args = sizeof...(Args);
using result_type = R;
template<size_t i>
struct arg {
using type = typename std::tuple_element<i,std::tuple<Args...>>::type;
};
};
template<typename T>
using Resultant = typename function_traits<T>::result_type;
template<class Cont, typename Map>
auto map(const Cont& cont, Map&& mapped) -> Rebind<Cont, Resultant<Unqualified<Map>>> {
Rebind<Cont, Resultant<Unqualified<Map>>> result;
auto result_iterator = std::back_inserter(result);
for(const auto& elem : cont) {
*result_iterator = mapped(elem);
}
return result;
}
#include <iostream>
int main() {
auto i = map(std::vector<int>{1,2,3,4,5,6}, [](int x) { return x % 2 == 0; });
for(auto&& j : i) {
std::cout << j << ' ';
}
}
输出:
0 1 0 1 0 1
Live version关于Coliru
答案 1 :(得分:4)
您可能正在寻找此语法
#include <algorithm>
#include <functional>
#include <type_traits>
#include <list>
template
<
template<typename, typename...> class Container,
typename InType,
typename FuncType,
typename... Rest
>
auto myMap (const Container<InType, Rest...>& container,
FuncType func) ->
Container<decltype(func(std::declval<InType>())), Rest...>
{
Container<decltype(func(std::declval<InType>())), Rest...> result;
std::transform(std::begin(container),
std::end(container),
std::back_inserter(result),
func);
return result;
}
虽然我不建议在任何真实的项目中使用这种代码。
答案 2 :(得分:0)
在C ++ 11中使用新的函数声明语法。
template <typename T>
auto myMap(const T &input,
std::function<typename U::value_type (typename T::value_type)> f ) -> decltype(...);
其中“...”被单个语句替换,然后编译器可以从中评估类型。困难的部分是在一个语句中定义返回类型,虽然来自Haskell你可能会想出来。
使用正常的函数声明,编译器没有任何方法可以从参数中推导出返回类型,因为参数尚未被解析。使用这种新语法,您可以使用参数来声明返回类型。
以下是您可以让编译器推导出在C ++ 98中无法实现的普通函数的返回类型的情况。
template <typename T1, typename T2>
auto add(T1 t1, T2 t2) -> decltype(t1+t2)
{ return t1 + t2; }
在C ++ 14中,他们添加了一些规则,允许编译器自动推导出返回类型,而不必使用decltype结构。
答案 3 :(得分:0)
n.m.的答案是我将其视为正确的答案,但我不认为嵌套模板对可用性的影响值得正确。
这是我的尝试:
template <typename T, typename F, typename C =
std::vector<typename std::result_of<F(typename T::value_type)>::type>>
C myMap(const T &input, F f) {
C ret;
for (const auto& x : input) {
ret.push_back(f(x));
}
return ret;
}
这说明了与原始代码的两个不同之处 - 一个,我没有指定std :: function,因为该类引入了很少需要的开销 - 模板将在您的示例中推断出不可解决的lambda类型。二,我指定std :: vector而不是std :: list作为默认返回类型(您的示例使用列表)。 vector是首选的C ++容器 - 使用它,除非你有一个非常强大的理由使用由基准测试支持的不同容器。
我上面的一个弱点是,很难或不可能列出作为模板先前参数的推导类型。在我看来,解决这个问题的最简单方法是提示:
template <class T> struct hint {};
template <typename T, typename F, typename C>
C myMap(const T& input, F f, hint<C> h) {
return myMap<T, F, C>(input, f);
}
当然,此时您也可以将输出容器作为输入。