以下代码均已在Coliru上使用其默认编译参数进行了尝试
g ++ -std = c ++ 17 -O2 -Wall -pedantic -pthread main.cpp && ./a.out
假设我们要创建一个包装器来保存数据的“容器”(例如vector
,list
,任何保存一个可以模板化的值的数据结构)。然后,稍后我们要使用辅助函数convert
将其转换为另一种类型。该函数可以被调用很多,只写convert(...)
而不是像convert<TypeToConvertTo>(...)
那样做模板会很好。在非MCVE中,我能够做到这一点,但是它需要std::function
参数和函数指针参数的绝对复制复制...这是不好的(除非它是必要的邪恶?希望不是)
还要假设我们有这个(请注意,诸如std::move
,explicit
之类的东西,通过std :: copy()在convert函数中进行复制等)。
尝试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:注意:候选:'模板DataContainer包装器:: convert(std :: function)[with V = V; VArgs = {VArgs ...}; DataContainer = std :: vector; T = int; TArgs = {std :: allocator}]'
DataContainer<V, VArgs...> convert(std::function<V(T)> f) { ^~~~~~~
main.cpp:17:36:注意:模板参数推导/替换失败:
main.cpp:37:57:注意:类型'std :: function'和'std :: __ cxx11 :: string()(long double)'不匹配的类型{aka'std :: __ cxx11 :: basic_string ()(long double)'}
vector<string> intVec = w.convert(std::to_string); ^
main.cpp:37:57:注意:类型'std :: function'和'std :: __ cxx11 :: string()(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>(...)
的convert类型,我希望编译器为我做。这可能吗?
尝试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); ^
main.cpp:33:36:注意:候选:'template DataContainer Wrapper :: convert(Func)[with Func = Func; V = V; VArgs = {VArgs ...}; DataContainer = std :: vector; T = int; TArgs = {std :: allocator}]'
DataContainer<V, VArgs...> convert(Func f) { ^~~~~~~
main.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:错误:没有匹配的函数可以调用“包装器> :: convert(main()::)”
vector<string> intVec = w.convert([](int i) { return std::to_string(i); }); ^
main.cpp:17:36:注意:候选:'模板DataContainer包装器:: convert(std :: function)[with V = V; VArgs = {VArgs ...}; DataContainer = std :: vector; T = int; TArgs = {std :: allocator}]'
DataContainer<V, VArgs...> convert(std::function<V(T)> f) { ^~~~~~~
main.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。
答案 0 :(得分:1)
这是每个人一直在犯的错误:
template <typename V>
DataContainer<V> convert(std::function<V(T)> f);
我了解人们为什么这么做,这显然是正确正确的做法吗?甚至写过here的内容。关键是:推论std::function
是总是错误的。您无法推断出这一点,因为它不允许进行转换,即使您可以推断出它,也会增加您不想要的开销。
不幸的是,没有等效的干净语言解决方案。而且,我的意思是说,T
和V
如此紧密地绑在一起,就像上面的非解决方案一样。
您真正想做的是:
template <typename F, typename V = std::invoke_result_t<F, T>>
DataContainer<V> convert(F f);
也就是说,我们只推导类型-然后使用类型特征确定答案是什么。
std::to_string
的问题是完全独立的-您无法将重载的函数或函数模板传递到函数模板中。您始终必须将其包装在lambda中。