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_string
has 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::string
probably 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