我有一个更高阶的函数map
,它类似于STL for_each
,并将std::function
个对象映射到vector
个事物上。
template<class T, class U>
vector<U> map(function<U (T)> f, vector<T> xs) {
vector<U> ret;
for (auto &x: xs)
ret.push_back(f(x));
return ret;
}
现在,我想让这个更高阶的函数同时使用function<int (const vector<T>&)>
和function<int (vector<T>)>
类型的对象,如附加的最小示例所示。
问题在于function<int (const vector<T>&)>
和function<int (vector<T>)>
似乎可以相互转换(请参阅head
和head2
),但map
不会const引用版本function<int (const vector<int>&)>
(参见Q1
)。
可以告诉map
接受带有显式转换(Q2
)的const引用版本,但这很麻烦。
我想知道,一般来说,是否可以编写一个函数deref
来删除function<int (const vector<T>&)>
中的const引用并返回function<int (vector<T>)>
?
(如果可以,那么我将不必为const refs写两个相同的重载/ map实现)。
感谢。
#include <vector>
#include <functional>
using namespace std;
template<class T, class U>
vector<U> map(function<U (T)> f, vector<T> xs) {
vector<U> ret;
for (auto &x: xs)
ret.push_back(f(x));
return ret;
}
int main() {
vector<vector<int>> m;
function<int (const vector<int>&)> head = [](const vector<int>& a) {return a[0];};
function<int (const vector<int>&)> head1 = [](vector<int> a) {return a[0];}; //conversion OK
function<int (vector<int>)> head2 = [](const vector<int>& a) {return a[0];}; //conversion OK
map(head2,m); //OK
map(head,m); //Q1: problem line, implicit conversion NOT OK
map(function<int (vector<int>)>(head),m); //Q2: explicit conversion OK
map(deref(head),m); //Q3: ??How-to, deref takes a std::function f and returns a function with const ref removed from its signature
return 0;
}
---编辑---
我对deref
类似的函数或元函数特别感兴趣,它可以从std::function
对象的类型签名中删除const ref,这样我至少可以{{1自动。
我知道,正如@Brian和@Manu正确指出的那样,使用Q2
指定类型并不常见,但我想知道我上面提到的内容是否可行。 就个人而言,我认为std::function
的代码更清晰,考虑到在C#中如何使用泛型函数类型std::function
。如果类型擦除的成本是可以容忍的话。
我完全同意c ++可以推断返回类型,并在类型错误时给出错误消息。也许这只是一个品味问题,我更愿意在编写函数签名时拼写出来。
答案 0 :(得分:21)
我理解你使用std::function
的原因:你必须知道转换的返回类型才能创建向量,对吧?
但考虑一种完全不同的方法。给定元函数std::result_of
,您可以计算函数调用的结果类型,所以只需写:
template<typename F , typename CONTAINER , typename T = typename std::result_of<F(typename CONTAINER::value_type)>::type>
std::vector<T> map( F f , CONTAINER&& container )
{
std::vector<T> result;
for( auto& e : container )
result.emplace_back( f( e ) );
return result;
}
不滥用std::function
:始终考虑std::function
的作用(即类型删除),不要将其用作通用函数类型。
依靠鸭子打字而不是类型上的耦合:别担心,如果出现问题,它也不会编译。
适用于任何标准库容器,因为我们使用value_type
特征提取了元素类型,而不是直接使用std::vector
。
代码更加清晰高效,这都是因为std::function
使用量减少了。
使用std::function
,您可以在几行中编写类似于Boost.OverloadedFunction的内容:
template<typename F , typename... Fs>
struct overloaded_function : public std_function<F> , public std_function<Fs>...
{
overloaded_function( F&& f , Fs&&... fs ) :
std_function<F>{ f },
std_function<Fs>{ fs }...
{}
};
其中std_function
是一个给定函数类型F
的元函数,返回std::function
实例,其签名为F
。我将其作为游戏/挑战留给读者。
多数民众赞成。使用类似make的功能改进它:
template<typename F , typename... Fs>
overloaded_function<F,Fs...> make_overloaded_function( F&& f , Fs&&... fs )
{
return { std::forward<F>( f ) , std::forward<Fs>( fs )... };
}
你准备好了:
auto f = make_overloaded_function( [](){ return 1; } ,
[](int,int){ return 2; } ,
[](const char*){ return 3; } );
f(); //Returns 1
f(1,2); //Returns 2
f("hello"); //Returns 3
好的,让我试试:std::decay
元函数应用在按值传递argumments时所做的衰减到给定类型。这包括删除cv限定符,删除引用等等。所以像你这样的元函数可能是一个采用函数签名类型并将衰减应用于其所有argumments的东西:
template<typename F>
struct function_decay;
template<typename R typename... ARGS>
struct function_decay<R(ARGS...)>
{
using type = R(typename std::decay<ARGS>::type...);
};
那应该做的工作。
我之所以写这篇文章是因为你在评论中明确要求它,但我强烈建议你使用我最初展示的替代方案,因为它与你的方式相比有许多优点。
也就是说,我希望这个答案有助于解决您的问题。
答案 1 :(得分:4)
惯用解决方案是简单地允许map
采用任意函数类型,
template<class T, class F>
auto map(F f, vector<T> xs) -> vector<typename result_of<F(T)>::type> {
vector<typename result_of<F(T)>::type> ret;
for (auto &x: xs)
ret.push_back(f(x));
return ret;
}
这种方法的主要问题是如果F
无法使用T
类型的参数调用,或者它返回奇怪的内容(例如void
),则会出现令人困惑的错误消息。 / p>
(第二个问题是map
的第一个参数不能是重载函数;编译器不能简单地选择带有T
类型参数的重载。 )
(您可能还想考虑衰减f
的返回类型。)