无法使用 std::function 作为参数类型(需要函数指针版本)宁愿像 STL 这样的模板,但随后它无法推断参数



以下代码都使用其默认编译参数在 Coliru 上进行了尝试

g++ -std=c++17 -O2 -Wall -pedantic -pthread main.cpp && ./a.out

假设我们要制作一个包装器,它保留数据的"容器"(例如,vectorlist,任何保存一个可以模板化的值的数据结构)。然后稍后我们想用一个辅助函数将其转换为另一种类型convert.这个函数可以被调用很多,最好只写convert(...),而不是像convert<TypeToConvertTo>(...)那样模板化它。在非 MCVE 中,我能够做到这一点,但它需要绝对丑陋的std::function参数和函数指针参数的复制粘贴......哪个不是善(除非是必要的恶?希望不是)

还假设我们有这个(注意像std::moveexplicit、通过 std::copy() 或类似的东西在转换函数中复制,等等)。

尝试 1:使用以下std::function失败:

#include <functional>
#include <iostream>
#include <string>
#include <type_traits>
#include <vector>
using std::string;
using std::vector;
template <template<typename...> class DataContainer, typename T, typename... TArgs>
struct Wrapper {
DataContainer<T, TArgs...> dataContainer;
Wrapper(DataContainer<T, TArgs...> container) : dataContainer(container) { }
// Using parameter type std::function<V(T)> fails here
template <typename V, typename... VArgs>
DataContainer<V, VArgs...> convert(std::function<V(T)> f) {
DataContainer<V, VArgs...> output;
for (T t : dataContainer)
output.emplace_back(f(t));
return output;
}
};
int main() {
vector<int> nums = {123};
Wrapper w{nums};
vector<string> intVec = w.convert(std::to_string);
std::cout << intVec.front() << std::endl;
}

这不会编译,并给出错误

main.cpp:在函数 'int main()' 中:

main.cpp:37:57:错误:调用"包装器>::convert()"没有匹配函数

vector<string> intVec = w.convert(std::to_string);
^

main.cpp:17:36: 注意:候选:'模板数据容器包装器::转换(std::function) [V = V;VArgs = {VArgs ...};数据容器 = 标准::向量;T = 整数;TArgs = {std::allocator}]'

DataContainer<V, VArgs...> convert(std::function<V(T)> f) {
^~~~~~~

主.cpp:17:36: 注意:模板参数推导/替换失败:

主.cpp:37:57:注意:不匹配的类型"std::function"和"std::__cxx11::字符串()(长双精度)"{又名"std::__cxx11::basic_string ()(长双精度)"}

vector<string> intVec = w.convert(std::to_string);
^

主.cpp:37:57:注意:不匹配的类型"std::function"和"std::__cxx11::字符串()(int)" {aka 'std::__cxx11::basic_string ()(int)'}

main.cpp:37:57:注意:无法推断模板参数"V">

我尝试在重载弄乱它的情况下制作自己的独立静态函数,但我得到同样的错误,说:

不匹配的类型 'std::function' 和 'int ()(std::__cxx11::string)'

{aka 'int ()(std::__cxx11::basic_string)'}

但是,当我使用以下命令将std::function更改为函数指针时:

// Now using V(*f)(T) instead
template <typename V, typename... VArgs>
DataContainer<V, VArgs...> convert(V(*f)(T)) {
DataContainer<V, VArgs...> output;
for (T t : dataContainer)
output.emplace_back(f(t));
return output;
}

这有效,现在可以打印出来

123

这给了我想要的东西,但现在我必须指定转换的类型,就像我希望编译器为我做的.convert<TypeHere>(...)。这可能吗?

尝试 2:

我想我可以做 STL 库所做的事情,它像这样模板函数:

template <typename Func, typename V, typename... VArgs>
DataContainer<V, VArgs...> convert(Func f) {
DataContainer<V, VArgs...> output;
for (T t : dataContainer)
output.emplace_back(f(t));
return output;
}

但随后编译失败,并显示:

main.cpp:在函数 'int main()' 中:

main.cpp:45:57:错误:调用"包装器>::convert()"没有匹配函数

vector<string> intVec = w.convert(std::to_string);
^

主.cpp:33:36: 注意:候选:'模板数据容器包装器::转换(Func) [使用 Func = Func;V = V;VArgs = {VArgs ...};数据容器 = 标准::向量;T = 整数;TArgs = {std::allocator}]'

DataContainer<V, VArgs...> convert(Func f) {
^~~~~~~

主.cpp:33:36: 注意:模板参数推导/替换失败:

main.cpp:45:57:注意:无法推断模板参数"Func">

vector<string> intVec = w.convert(std::to_string);
^

我想要的只是为了这条线

vector<string> intVec = w.convert(std::to_string);

无需我在convert右侧写入任何模板即可工作.

我做错了什么?我可以用函数指针得到我想要的东西,但似乎我必须写一堆重载,比如V(*f)(T)V(*f)(const &T)

尝试 3:

除非我指定类型,否则 Lambda 不起作用,这意味着我必须这样做:

vector<string> intVec = w.convert<string>([](int i) { return std::to_string(i); });

我想要:

vector<string> intVec = w.convert([](int i) { return std::to_string(i); });

当我不指定类型时会发生这种情况(为什么它不能推断它?

main.cpp:在函数 'int main()' 中:

main.cpp:45:82:错误:调用"包装器>::转换(main()::)"没有匹配函数

vector<string> intVec = w.convert([](int i) { return std::to_string(i); });
        ^

main.cpp:17:36: 注意:候选:'模板数据容器包装器::转换(std::function) [V = V;VArgs = {VArgs ...};数据容器 = 标准::向量;T = 整数;TArgs = {std::allocator}]'

DataContainer<V, VArgs...> convert(std::function<V(T)> f) {
^~~~~~~

主.cpp:17:36: 注意:模板参数推导/替换失败:

main.cpp:45:82:注意:"main()::"不是从"std::function"派生的

vector<string> intVec = w.convert([](int i) { return std::to_string(i); });
        ^

简而言之,所有这些都是因为我必须不断复制粘贴每个使用函数指针和 std::function 重载执行此类操作的函数,然后如果我想支持右值、左值、常量左值的函数参数......我得到了函数的组合爆炸,它使工作解决方案变得一团糟。我不知道这是否是不可避免的,或者我是否缺少一些东西来让它推断类型,同时允许我传入函数名称或 lambda。

这是每个人都会犯的错误,一直都在:

template <typename V>
DataContainer<V> convert(std::function<V(T)> f);

我理解人们为什么这样做,这显然是正确的方法?甚至在这里详细写了这个。关键是:推论std::function总是错误的。您无法推断出这一点,因为它不允许转换,即使您可以推断出它,它也会增加您不想要的开销。

不幸的是,没有同等干净的语言解决方案。我所说的干净是指,在某种程度上,TV如此清楚地联系在一起,就像上面的非解决方案一样。

你真正想做的是这样的:

template <typename F, typename V = std::invoke_result_t<F, T>>
DataContainer<V> convert(F f);

也就是说,我们只推断出类型 - 然后使用类型特征来找出答案是什么。


std::to_string的问题完全是独立的 - 您只是无法将重载的函数或函数模板传递到函数模板中。你总是必须把它包裹在一个lambda中。

最新更新