The following code was all tried on Coliru with its default compilation arguments
g++ -std=c++17 -O2 -Wall -pedantic -pthread main.cpp && ./a.out
Suppose we want to make a wrapper that holds on to a 'container' of data (ex, vector, list, any data structure that holds one value that can be templated). Then later on we want to convert it to another type with a helper function convert. This function can get called a lot, it would be nice to just write convert(...), and not template it like convert<TypeToConvertTo>(...). In a non MCVE, I am able to do this but it requires absolutely ugly copy-pasta of std::function parameters and function pointer parameters... which is not good (unless its a necessary evil? hopefully not)
Also suppose we have this (note that things like std::move, explicit, copying in the convert function by std::copy() or something like that, etc).
Attempt 1: Using std::function below fails:
#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;
}
This does not compile, and gives the error
main.cpp: In function 'int main()':
main.cpp:37:57: error: no matching function for call to 'Wrapper >::convert()'
vector<string> intVec = w.convert(std::to_string); ^main.cpp:17:36: note: candidate: 'template DataContainer Wrapper::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: note: template argument deduction/substitution failed:
main.cpp:37:57: note: mismatched types 'std::function' and 'std::__cxx11::string ()(long double)' {aka 'std::__cxx11::basic_string ()(long double)'}
vector<string> intVec = w.convert(std::to_string); ^main.cpp:37:57: note: mismatched types 'std::function' and 'std::__cxx11::string ()(int)' {aka 'std::__cxx11::basic_string ()(int)'}
main.cpp:37:57: note: couldn't deduce template parameter 'V'
I tried making my own standalone static function in the case that overloads are messing with it, but I get the same kind of error, saying:
mismatched types 'std::function' and 'int ()(std::__cxx11::string)' {aka 'int ()(std::__cxx11::basic_string)'}
However when I change the std::function to a function pointer with:
// 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;
}
This works and now prints out
123
This gives me what I want, but now I have to specify the type on convert like .convert<TypeHere>(...) which I'd like the compiler to do for me. Is this possible?
Attempt 2:
I thought I could do what the STL library does, where it templates the function like so:
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;
}
but then compilation fails with:
main.cpp: In function 'int main()':
main.cpp:45:57: error: no matching function for call to 'Wrapper >::convert()'
vector<string> intVec = w.convert(std::to_string); ^main.cpp:33:36: note: candidate: '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: note: template argument deduction/substitution failed:
main.cpp:45:57: note: couldn't deduce template parameter 'Func'
vector<string> intVec = w.convert(std::to_string); ^
All I want is for the line
vector<string> intVec = w.convert(std::to_string);
to work without me having to write in any templates to the right of convert.
What am I doing wrong? I can get what I want with function pointers but then it seems like I have to write a bunch of overloads like V(*f)(T), V(*f)(const &T)
Attempt 3:
Lambdas don't work unless I specify the type, meaning I have to do this:
vector<string> intVec = w.convert<string>([](int i) { return std::to_string(i); });
and I want:
vector<string> intVec = w.convert([](int i) { return std::to_string(i); });
which occurs when I don't specify the type (why can't it deduce it?)
main.cpp: In function 'int main()':
main.cpp:45:82: error: no matching function for call to 'Wrapper >::convert(main()::)'
vector<string> intVec = w.convert([](int i) { return std::to_string(i); }); ^main.cpp:17:36: note: candidate: 'template DataContainer Wrapper::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: note: template argument deduction/substitution failed:
main.cpp:45:82: note: 'main()::' is not derived from 'std::function'
vector<string> intVec = w.convert([](int i) { return std::to_string(i); }); ^
In short, all of these are done because I have to keep copy pasting every function that does this kind of thing with a function pointer and a std::function overload, and then if I want to support function arguments that are rvalues, lvalues, const lvalues... I'm getting a combinatoric explosion of functions and it's making the working solution a mess. I don't know if this is unavoidable, or if there's something I'm missing to get it to deduce the type while allowing me to pass in either a function name or lambda.
to_stringhas different overload, which one do you want to use? - Matthieu BrucherVArgs...supposed to be? - Barryint f(string s) { return 0; }, my problem still exists. In retrospectstd::stringprobably was a bad choice because people might end up focusing on the wrong thing. - WaterVArgs...just deduces as empty since you're not providing anything. - Barry