Constraints on template types - c++

Suppose, I want to implement a generic higher-order Map function in C++. Map should take a container and a transformation function and return a container of the same type, but possibly with different type of items.
Let's take vector for instance:
template <typename InT, typename OutT, typename Tr>
vector<OutT> Map(vector<InT> cont, Tr tr)
{
OutCont out(cont.size());
auto oit = out.begin();
for (auto it = cont.cbegin(); it != cont.cend(); ++it, ++ot)
{
*oit = tr(*it);
}
}
which I want to use like this:
vector<int> v(10);
std::iota(v.begin(), v.end(), 0);
auto x = Map(v, [](int x) -> int {return x * 2;});
This fails in VC++ 2012, giving me the following error:
error C2783: 'std::vector<OutT> Map(std::vector<_Ty>,Tr)' : could not deduce template argument for 'OutT'
It seems to me that the compiler has all the necessary information, because I explicitly defined the return type in the lambda. Is there a way around this?
The above example uses vector. Is there a way to use a generic type, so that the input and output types are the same? For instance if I have an input container defined as vector<string> and the transformation function tr(string a) -> int, then my goal is to make the compiler to figure out the output type to be vector<int>. Here's the pseudo-code for what I want to achieve:
template <typename Cont<InT>, typename Cont<OutT>, typename Tr<InT, OutT>>
Cont<OutT> Map(Cont<InT> cont, Tr<InT, OutT> tr)
{
// Implementation
}

You may write something like:
template <typename InT, typename Tr>
auto Map(std::vector<InT> cont, Tr tr) -> std::vector<decltype(tr(cont[0]))>
{
std::vector<decltype(tr(cont[0]))> out(cont.size());
auto oit = out.begin();
for (auto it = cont.cbegin(); it != cont.cend(); ++it, ++oit)
{
*oit = tr(*it);
}
return out;
}
Out type is deduced.
[Edit]
For a more generic function with more container:
template <template<typename, typename...> class Container, typename InT, typename Tr, typename... Args>
auto Map(const Container<InT, Args...>& cont, Tr tr) -> Container<decltype(tr(cont[0])), Args...>
{
Container<decltype(tr(cont[0])), Args...> out(cont.size());
auto oit = out.begin();
for (auto it = cont.cbegin(); it != cont.cend(); ++it, ++oit)
{
*oit = tr(*it);
}
return out;
}
Notice the typename... needed because std::vector can also take allocator

Related

Canonical C++ way to control function return type with sensible default

What is the canonical way to control the return type of a function based on an input type with a sensible default?
e.g.
// by default want R = decltype(container)::value_type
template<typename R>
R func(const auto& container)
{
R result{};
// some calculations using container
return result;
}
The usage would be:
std::vector<int> ct{};
func(ct); // default result -> int
func<double>(ct); // custom call -> double
I could drop the use of auto and use another template argument:
template<typename C, typename R = C::value_type>
R func(const C& container)
{
R result{};
// some calculations using container
return result;
}
But then usage for the control case is less than ideal:
func<std::vector<int>, double>(ct); // custom call -> double
I could reorder the template arguments:
template<typename R, typename C>
R func(const C& container)
{
R result{};
// some calculations using container
return result;
}
But then I lose my default behaviour:
func(ct); // no longer works
func<int>(ct); // required <int>
I don't know about canonical C++, but this works:
template<typename R=void>
auto func(const auto& container)
{
using Out = std::conditional_t<
std::is_same_v<R, void>,
typename std::decay_t<decltype(container)>::value_type,
R>;
Out result{};
// some calculations using container
return result;
}
godbolt
You can combine function template overloading with SFINAE to handle both cases:
template<typename C, typename R = typename C::value_type>
R func(C const& container) {
R result{};
// some calculations using container
return result;
}
template<typename R, typename C>
R func(C const& container) {
return func<C, R>(container);
}
godbolt
You could work around the problem by using auto as the return type. Something like
// by default want R = decltype(container)::value_type
template<typename R = void>
auto func(const auto& container)
{
// if R is void use value_type otherwise use R
using RetType = std::conditional_t<std::is_same_v<R, void>, typename std::remove_cvref_t<decltype(container)>::value_type, R>;
RetType result{};
// some calculations using container
return result;
}
If you always want to return something you can use void as sentinel and defer deduction of the return type to the body of the function:
#include <type_traits>
template <typename T,typename Select>
struct select_default_or_T{
using type = std::conditional_t< std::is_same_v<void,Select> , typename T::value_type,Select>::type;
};
template<typename R = void>
auto func(const auto& container)
{
using R = typename select_default_or_T<decltype(container),R>::type;
R result{};
// some calculations using container
return result;
}
Well, the name of the traits needs a fix, but I think you get the idea.

how can I get this function template to work reguardless of whether I pass a string literal or a string object?

I have the following function template that takes any type of map and returns either the value associated with a certain key or a default value provided at the call site:
template <template <typename Key, typename Val, typename ...Args> typename C, typename Key, typename Val, typename ...Args>
Val get_or_default(C<Key, Val, Args...> const& my_map, Key const& k, Val const& v)
{
typename C<Key, Val, Args...>::const_iterator const it = my_map.find(k);
return (it != my_map.end()) ? it->second : v;
}
when I call the function this way:
// initialize map
std::map<std::string, int> m1{ {"jim",1},{"mark",2},{"sally",3} };
// call get_or_default with std::string as key arg
std::cout << get_or_default(m1, std::string("jim"), -1) << std::endl;
everything works as expected, however when I call the function like:
std::cout << get_or_default(m1, "jim", -1) << std::endl;
I get the following error message:
error C2782: 'Val get_or_default(const C<Key,Val,Args...> &,const Key &,const Val &)': template parameter 'Key' is ambiguous
I think that the parameter Key is ambiguous because it is being called with std::string in the map template but as const char* for the second parameter of get_or_default
How can I get this function to work regardless of whether I am passing a string object or a string literal?
Thank you for any help at all.
Since you are getting conflicting deduction for the Key template parameter, you can simply make one of the Key arguments a non-deduced context.
First provide a simple type-identity struct:
template<typename T>
struct I { using type = T; };
and then use it like this:
template <template <typename Key, typename Val, typename ...Args> typename C,typename Key, typename Val, typename ...Args>
Val get_or_default(C<Key, Val, Args...> const& my_map, typename I<Key>::type const &k, Val const& v)
{
typename C<Key, Val, Args...>::const_iterator const it = my_map.find(k);
return (it != my_map.end()) ? it->second : v;
}
You can simplify this a bit. Remove the template template parameter names, since they are not used anyway. Also, you can use auto for the iterator type. Also, the variadic arguments don't seem to be used for any purpose.
template <template <typename, typename> typename C, typename Key, typename Val>
Val get_or_default(C<Key, Val> const& my_map, typename I<Key>::type const &k, Val const& v)
{
auto const it = my_map.find(k);
return (it != my_map.end()) ? it->second : v;
}
Here's a working demo.
This should work for you :
template<class C, class Key, class Val>
Val get_or_default(C const& mp, Key const& k, Val const& v)
{
typename C::const_iterator it = mp.find(k);
return (it != mp.end()) ? it->second : v;
}
If you want better type restrictions and error messages and c++ 20 is an option then concepts are the way to go

Combine multiple vectors (results of function) into one with template

I'd like to have a templated function taking in a vector<T> v and a function op, mapping T to vector<U> and would like to concatenate the results of applying f to every element vector of v to return a vector<U> = [ Elements of op(v[0]), Elements of op(v[1]) ...].
A working option I found was adding an example in the function to allow for template deduction:
template <typename Container>
Container& concat(Container& c1, Container const& c2) {
c1.insert(end(c1), begin(c2), end(c2));
return c1;
}
template <typename Container, typename UnaryOperation, typename U>
inline auto to_vec_from_vectors(Container& c, UnaryOperation&& op, U& ex)
-> std::vector<U> {
std::vector<U> v;
for (auto& e : c) {
std::vector<U> opv = op(e);
concat(v, opv);
}
return v;
}
But naturally I'd like to produce the same result with only the two parameters.
My attempt [replacing U with decltype(*std::begin(op(*std::begin(c))))]:
template <typename Container, typename UnaryOperation, typename U>
inline auto to_vec_from_vectors(Container& c, UnaryOperation&& op, U& ex)
-> std::vector<decltype(*std::begin(op(*std::begin(c))))> {
std::vector<decltype(*std::begin(op(*std::begin(c))))> v;
for (auto& e : c) {
std::vector<decltype(*std::begin(op(*std::begin(c))))> opv = op(e);
concat(v, opv);
}
return v;
}
Unfortunately this didn't compile. I'm also worried of wasting time if op is complex method.
This gave:
error: conversion from ‘std::vector<U>’ to non-scalar type ‘std::vector<const U&, std::allocator<const U&> >’ requested
error: forming pointer to reference type ‘const U&
...
so it seems to be related to 'const'.
How would this variant be corrected? Are there better alternatives?
Dereferencing a container iterator yields a reference (or a const reference, if the container was const), which is why decltype(*std::begin(op(*std::begin(c)))) yields const U& according to your compiler error (and not U).
You can fix this by either removing the reference again with std::remove_reference (or, if you want to also remove const and volatile, std::remove_cvref), or by just asking the vector for what it actually stores:
decltype(*std::begin(op(*std::begin(c)))) -> typename decltype(op(*std::begin(c)))::value_type
I have gone ahead and removed the unneeded U& ex parameter.
template <typename Container, typename UnaryOperation>
inline auto to_vec_from_vectors(Container& c, UnaryOperation&& op)
-> std::vector<typename decltype(op(*std::begin(c)))::value_type> {
std::vector<typename decltype(op(*std::begin(c)))::value_type> v;
for (auto& e : c) {
std::vector<typename decltype(op(*std::begin(c)))::value_type> opv = op(e);
concat(v, opv);
}
return v;
}
Demo
You can also avoid the triple repetition of the decltype chant by naming it:
template <typename Container, typename UnaryOperation>
using applied_op_t = typename decltype(std::declval<UnaryOperation>()(*std::begin(std::declval<Container>())))::value_type;

Equivalent of python map function using lambda

I am wondering if it is possible to write a C++ equivalent of the Python function map, using the automatic return type deduction feature. What I have in mind is something like this:
vector<int> input({1,2,3});
auto output=apply(input,[](int num){return num*num;});
//output should be a vector {1,4,9}
I do know about std::transform, but in the present state of affairs, writing a range loop seems easier.
Baum mit Augen's answer is most of the way there. Just takes a few more steps to support anything that is for-each-able:
template <typename C, typename F>
auto apply(C&& container, F&& func)
{
using std::begin;
using std::end;
using E = std::decay_t<decltype(std::forward<F>(func)(
*begin(std::forward<C>(container))))>;
std::vector<E> result;
auto first = begin(std::forward<C>(container));
auto last = end(std::forward<C>(container));
result.reserve(std::distance(first, last));
for (; first != last; ++first) {
result.push_back(std::forward<F>(func)(*first));
}
return result;
}
We can even go one step further and make this SFINAE-able by not using C++14 auto deduction and instead moving the failure up to the deduction phase. Start with a helper for begin/end:
namespace adl_helper {
using std::begin;
using std::end;
template <typename C>
auto adl_begin(C&& c) -> decltype(begin(std::forward<C>(c))) {
return begin(std::forward<C>(c));
}
template <typename C>
auto adl_end(C&& c) -> decltype(end(std::forward<C>(c))) {
return end(std::forward<C>(c));
}
}
using adl_helper::adl_begin;
using adl_helper::adl_end;
And then use that to deduce E earlier:
using adl_helper::adl_begin;
using adl_helper::adl_end;
template <typename C,
typename F,
typename E = std::decay_t<decltype(std::declval<F>()(
*adl_begin(std::declval<C>())
))>
>
std::vector<E> apply(C&& container, F&& func)
{
/* mostly same as before, except using adl_begin/end instead
of unqualified begin/end with using
*/
}
Now we can test at compile time if some container/function pair is apply-able, and the error is a deduction failure instead of a usage failre:
int arr[] = {1, 2, 3};
auto x = apply(arr, []{ return 'A'; });
main.cpp: In function 'int main()':
main.cpp:45:52: error: no matching function for call to 'apply(int [3], main()::<lambda()>)'
auto x = apply(arr, []() -> char { return 'A'; });
^
main.cpp:29:16: note: candidate: template<class C, class F, class E> std::vector<E> apply(C&&, F&&)
std::vector<E> apply(C&& container, F&& func)
^
main.cpp:29:16: note: template argument deduction/substitution failed:
main.cpp:25:50: error: no match for call to '(main()::<lambda()>) (int&)'
typename E = decltype(std::declval<F>()(
^
As pointed out, this would not handle a container of input iterators well. So let's fix it. We need something to determine the size of the container. If the container has a size() member function, we can use that. Otherwise if the iterators do not have category input_iterator_tag (don't know of any other way to distinguish input iterators...), we can use that. Otherwise, we're kind of out of luck. A good way of doing decreasing order of preference like this is to introduce a chooser hierarchy:
namespace details {
template <int I> struct chooser : chooser<I-1> { };
template <> struct chooser<0> { };
}
And then just walk down:
namespace details {
template <typename C>
auto size(C& container, chooser<2>) -> decltype(container.size(), void())
{
return container.size();
}
template <typename C,
typename It = decltype(adl_begin(std::declval<C&>()))
>
auto size(C& container, chooser<1>)
-> std::enable_if_t<
!std::is_same<std::input_iterator_tag,
typename std::iterator_traits<It>::iterator_category
>::value,
size_t>
{
return std::distance(adl_begin(container), adl_end(container));
}
template <typename C>
size_t size(C& container, chooser<0>)
{
return 1; // well, we have no idea
}
}
template <typename C>
size_t size(C& container)
{
return size(container, details::chooser<10>{});
}
Then we can use size() to reserve() our vector to the best of our ability:
template <typename C,
typename F,
typename E = std::decay_t<decltype(std::declval<F>()(
*adl_begin(std::declval<C>())
))>
>
std::vector<E> apply(C&& container, F&& func)
{
std::vector<E> result;
result.reserve(size(container));
for (auto&& elem : container) {
result.push_back(std::forward<F>(func)(std::forward<decltype(elem)>(elem)));
}
return result;
}
This can certainly be done and would probably look something like this:
template <class Container, class Function>
auto apply (const Container &cont, Function fun) {
std::vector< typename
std::result_of<Function(const typename Container::value_type&)>::type> ret;
ret.reserve(cont.size());
for (const auto &v : cont) {
ret.push_back(fun(v));
}
return ret;
}
If you want to be super general and handle C arrays and everything, you might need to add a couple of overloads for the special cases.
Live example
This has already been discussed in comments, but I think it should also be given as an answer:
The function std::transform from <algorithm> does what you want.
The following code works for me:
#include <vector>
#include <algorithm>
using namespace std;
//...
vector<int> input({1,2,3});
transform(input.begin(), input.end(), input.begin(), [](int num){return num*num;});
This works with your example, and with most of the containers. I use std::transform, because it can be optimized for each stl iterator. I started out from Baum mit Augen's answer, which was deleted later.
template<typename Container, typename Function>
using _mapT = std::vector<typename std::result_of<Function(const typename Container::value_type&)>::type>;
template <typename Container, typename Function>
_mapT<Container, Function> map(const Container &container, Function &&f)
{
_mapT<Container, Function> ret; ret.reserve(container.size());
std::transform(container.begin(), container.end(), std::back_inserter(ret), std::forward<Function>(f));
return ret;
}

Compiler does not deduce template parameters (map std::vector -> std::vector)

I have the following template.
template<typename T, typename U>
std::vector<U> map(const std::vector<T> &v, std::function<U(const T&)> f) {
std::vector<U> res;
res.reserve(v.size());
std::transform(std::begin(v), std::end(v), std::end(res), f);
return res;
}
When I use it in my code I have the specify the template parameters. Why is the compiler not able to deduce this for me? How do I have to change my template definition to make this work?
vector<int> numbers = { 1, 3, 5 };
// vector<string> strings = map(numbers, [] (int x) { return string(x,'X'); });
vector<string> strings = map<int, string>(numbers, [] (int x) { return string(x,'X'); });
Runnable code: http://ideone.com/FjGnxd
The original code in this question comes from here: The std::transform-like function that returns transformed container
Your function expects an std::function argument, but you're calling it with a lambda expression instead. The two are not the same type. A lambda is convertible to std::function, but template argument deduction requires exact matches and user defined conversions are not considered. Hence the deduction failure.
Deduction does work if you actually pass an std::function to map().
std::function<string(int const&)> fn = [] (int x) { return string(x,'X'); };
vector<string> strings = map(numbers, fn);
Live demo
One possible workaround to avoid having to specify the template arguments is to modify the function to accept any kind of callable, rather than an std::function object.
template<typename T, typename Func>
std::vector<typename std::result_of<Func(T)>::type>
map(const std::vector<T> &v, Func f) {
// ...
}
Another version of the same idea, using decltype and declval instead of result_of
template<typename T, typename Func>
std::vector<decltype(std::declval<Func>()(std::declval<T>()))>
map(const std::vector<T> &v, Func f) {
// ...
}
Finally, using a trailing return type
template<typename T, typename Func>
auto map(const std::vector<T> &v, Func f)
-> std::vector<decltype(f(v[0]))> {
// ...
}
Live demo