I am revisiting C++ after a long hiatus, and I would like to use templates to design the known "map" function -- the one which applies a function to every element of a collection.
Disregarding the fact my map doesn't return anything (a non-factor here), I have managed to implement what I wanted if the function passed to "map" does not need to accept additional arguments:
#include <iostream>
template <typename C, void fn(const typename C::value_type &)> void map(const C & c) {
for(auto i : c) {
fn(i);
}
}
struct some_container_type { /// Just some hastily put together iterable structure type
typedef int value_type;
value_type * a;
int n;
some_container_type(value_type * a, int n): a(a), n(n) { }
value_type * begin() const {
return a;
}
value_type * end() const {
return a + n;
}
};
void some_fn(const int & e) { /// A function used for testing the "map" function
std::cout << "`fn` called for " << e << std::endl;
}
int main() {
int a[] = { 5, 7, 12 };
const some_container_type sc(a, std::size(a));
map<some_container_type, some_fn>(sc);
}
However, I would like map to accept additional arguments to call fn with. I've tried to compile the modified variant of the program (container type definition was unchanged):
template <typename C, typename ... T, void fn(const typename C::value_type &, T ...)> void map(const C & c, T ... args) {
for(auto i : c) {
fn(i, args...);
}
}
void some_fn(const int & e, int a, float b, char c) {
std::cout << "`fn` called for " << e << std::endl;
}
int main() {
int a[] = { 5, 7, 12 };
const some_container_type sc(a, std::size(a));
map<some_container_type, int, float, char, some_fn>(sc, 1, 2.0f, '3');
}
But gcc -std=c++20 refuses to compile the modified program containing the above variant, aborting with:
<source>: In function 'int main()':
<source>:29:56: error: no matching function for call to 'map<some_container_type, int, float, char, some_fn>(const some_container_type&, int, int, int)'
29 | map<some_container_type, int, float, char, some_fn>(sc, 1, 2, 3);
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~
<source>:16:97: note: candidate: 'template<class C, class ... T, void (* fn)(const typename C::value_type&, T ...)> void map(const C&, T ...)'
16 | template <typename C, typename ... T, void fn(const typename C::value_type &, T ... args)> void map(const C & c, T ... args) {
| ^~~
<source>:16:97: note: template argument deduction/substitution failed:
<source>:29:56: error: type/value mismatch at argument 2 in template parameter list for 'template<class C, class ... T, void (* fn)(const typename C::value_type&, T ...)> void map(const C&, T ...)'
29 | map<some_container_type, int, float, char, some_fn>(sc, 1, 2, 3);
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~
<source>:29:56: note: expected a type, got 'some_fn'
Microsoft Visual C++ compiler (19.24.28314) gives a more descriptive error message:
error C3547: template parameter 'fn' cannot be used because it follows a template parameter pack and cannot be deduced from the function parameters of 'map'
Can someone explain if and how I can idiomatically accomplish for map to accept arbitrary arguments for forwarding these to fn?
I know I can pass fn to the map function as argument instead of specifying it as an argument to the template, but for reasons related to inlining and to better understand C++ templates, I'd like to retain fn a template rather than a function parameter.
I also don't want to use any libraries, including the standard library (what use of std I show in the examples above is only for clarifying the question). I know there are "functor" and "forward" somewhere in the libraries, but I suppose they too were written in C++, so I am curious if my problem can be solved without any libraries.
A simple way to fix this would be to deduce the non-type template parameter for the function, and reorder the template parameter list
template <typename C, auto fn, typename ... T>
void map(const C & c, T ... args) {
for(auto i : c) {
fn(i, args...);
}
}
and then call it like this
map<some_container_type, some_fn, int, float, char>(sc, 1, 2.0f, '3');
Here's a demo
You could also move fn to the beginning of the template parameter list.
template <auto fn, typename C, typename ... T>
void map(const C & c, T ... args) {
for(auto i : c) {
fn(i, args...);
}
}
Now since C and T can be deduced from the function arguments, this makes the call site much cleaner
map<some_fn>(sc, 1, 2.0f, '3');
Here's a demo
Related
My goal is to be able to include my own extra type at declaration and have it passed to my template function. How would I be able to declare my type so that the compiler would not drop my extra template parameter.
For example I have this code:
#include <iostream>
#include <vector>
// my_vector is an alias for std::vector<T> that also takes an extra type E
template<typename T, typename E>
using my_vector = std::vector<T>;
// my aliased type is being demoted to std::vector<T, std::allocator<T> >
template<typename T, typename E>
void write_to(std::ostream stream, const my_vector<T, E>& vec) {
// I need E type for somthing here for example this
stream << static_cast<E>(vec.size());
for (auto elm : vec) {
stream << elm;
}
}
int main() {
// very redundantly declaring that I want my_vector
my_vector<float, uint8_t> vec = my_vector<float, uint8_t>{ 1.0f, 2.0f, 3.0f };
write_to(std::cout, vec);
// this would compile if I called write_to<uint8_t> but I want this to be assumed by the compiler
}
g++ output suggests that it is not passing my_vector<T, E> to write_to but instead drops my_vector altogether and instead passes std::vector<T, std::allocator<T> >, is it possible to get the compiler to not drop the extra template parameter so that I dont have to explicitly include it in every call of write_to here?
Here is my output from g++ std=c++17
[arkav:~/devel/packetize] $g++ template_demote.cc --std=c++17
template_demote.cc: In function ‘int main()’:
template_demote.cc:21:25: error: no matching function for call to ‘write_to(std::ostream&, my_vector<float, unsigned char>&)’
21 | write_to(std::cout, vec); // this would compile if I called write_to<uint8_t> but I want this to be assumed by the compiler
| ^
template_demote.cc:10:6: note: candidate: ‘template<class T, class E> void write_to(std::ostream, my_vector<T, E>&)’
10 | void write_to(std::ostream stream, const my_vector<T, E>& vec) {
| ^~~~~~~~
template_demote.cc:10:6: note: template argument deduction/substitution failed:
template_demote.cc:21:25: note: couldn’t deduce template parameter ‘E’
21 | write_to(std::cout, vec); // this would compile if I called write_to<uint8_t> but I want this to be assumed by the compiler
| ^
Solution
Inherent std::vector and its constructor in my type definition
template<typename E, typename T>
class my_vector: public std::vector {
using std::vector<T>::vector;
};
You can try something like this:
#include <iostream>
#include <vector>
template<class T, class E>
struct util {
std::vector<T> my_vector;
void write_to(std::ostream& stream) {
stream << static_cast<E>(my_vector.size());
for (auto elm : my_vector) {
stream << elm;
}
}
};
int main() {
util<float, uint8_t> u;
u.my_vector = std::vector<float>{ 1.0f, 2.0f, 3.0f };
u.write_to(std::cout);
}
You can read in cppreference that, when aliasing:
Alias templates are never deduced by template argument deduction when deducing a template template parameter.
Check the below code.
#include <future>
template <class F, class... Args>
void do_something(F f, Args... args) {
using return_type = typename std::result_of<F(Args...)>::type;
// Why below gives an error?
std::packaged_task<return_type(Args...)> task(f, args...);
}
int func(int a, int b) {
}
int main() {
do_something(func, 1, 2);
}
The packaged_task constructor gives a following error.
error: no matching function for call to 'std::packaged_task<int(int, int)>::packaged_task(int (*&)(int, int), int&, int&)'
8 | std::packaged_task<return_type(Args...)> task(f, args...);
The thing I don't understand is that why f and args became a reference type in the constructor? The Args... were int, int types whereas args... just became int&, int&. Where is this coming from?
packaged_task do not have the signature you want.
compiler is saying there is no such function. (probably with candidates below it)
I'm working on a function which invokes a supplied function with a variable number of arguments. It compiles and works correctly on Visual Studio 2015, but fails to compile on Clang . I've prepared a demonstration which shows what I'm trying to do. The error I get in Clang is:
prog.cpp: In function 'int main()': prog.cpp:31:2: error: no matching
function for call to 'run(std::vector&, void ()(int&, const
int&), const int&)' ); ^ prog.cpp:7:6: note: candidate:
template void
run(std::vector&, const std::function&,
mutrArgs ...) void run(
^ prog.cpp:7:6: note: template argument deduction/substitution failed: prog.cpp:31:2: note: mismatched types 'const
std::function' and 'void ()(int&, const
int&)' );
#include <functional>
#include <iostream>
#include <vector>
using namespace std;
template<int RepeatTimes, class ... mutrArgs>
void run(
vector<int>& vec,
const function<void(int&, mutrArgs ...)>& mutr,
mutrArgs ... args
)
{
for (int times{0} ; times < RepeatTimes ; ++times)
for (auto& item : vec)
mutr(item, args...);
}
void adder(int& i, const int& val)
{
i += val;
}
int main()
{
vector<int> v{0,1,2,3,4,5,6,7,8,9};
const int addValue{4};
run<2, const int&>(
v,
&adder,
addValue
);
for (auto i : v)
cout << i << " ";
cout << endl;
return 0;
}
run<2, const int&> just state the first argument, but doesn't deactivate deduction.
run<2, const int&>(v, &adder, addValue);
has 2 places to deduce mutrArgs:
addValue -> mutrArgs = { const int& }
&adder which is not a std::function and so fail.
Taking address of function fix that problem
auto call_run = &run<2, const int&>;
call_run(v, &adder, addValue);
Strangely, clang doesn't support the inlined usage contrary to gcc :/
(&run<2, const int&>)(v, &adder, addValue);
If you want to disable deduction, you may make your template arg non deducible:
template <typename T> struct identity { using type = T; };
template <typename T> using non_deducible_t = typename identity<T>::type;
And then
template<int RepeatTimes, class ... mutrArgs>
void run(
std::vector<int>& vec,
const std::function<void(int&, non_deducible_t<mutrArgs> ...)>& mutr,
non_deducible_t<mutrArgs> ... args
)
Demo
Even if in your case a simple typename F as suggested by Joachim Pileborg seems better.
If you look at all standard library algorithm function, at least the ones taking a "predicate" (a callable object) they take that argument as a templated type.
If you do the same it will build:
template<int RepeatTimes, typename F, class ... mutrArgs>
void run(
vector<int>& vec,
F mutr,
mutrArgs ... args
)
{
...
}
See here for an example of you code. Note that you don't need to provide all template arguments, the compiler is able to deduce them.
The following code is a minimum working (or perhaps non-working) example.
What it does is basically encapsulates a bunch of std::map structures as private members in a base class. To avoid writing a lot of setters and getters, they are implemented as template functions.
// test.cpp
#include <map>
#include <iostream>
enum class E0
{
F0, F1, F2,
};
The declaration of the base class.
using std::map;
class P_base
{
private:
map<E0, int> m_imap;
// ...
// ... Other std::map members with different key types and value types.
public:
map<E0, int> & imap;
// ...
// ... Other std::map references.
P_base() : imap(m_imap) {}
template<typename map_type, typename key_type, typename val_type>
void set(map_type & m, const key_type & k, const val_type & v)
{
m[k] = v;
}
template<typename map_type, typename key_type>
auto access_to_map(const map_type & m, const key_type & k) -> decltype(m.at(k))
{
return m.at(k);
}
};
class P : private P_base
{
public:
decltype(P_base::imap) & imap;
P() : P_base(), imap(P_base::imap) {}
template<typename map_type, typename key_type, typename val_type>
void set(map_type & m, const key_type & k, const val_type & v)
{
P_base::set(m, k, v);
}
template<typename map_type, typename key_type>
auto access_to_map(const map_type & m, const key_type & k) -> decltype(P_base::access_to_map(m, k))
{
return P_base::access_to_map(m, k);
}
};
main
int main(int argc, const char * argv[])
{
using std::cout;
using std::endl;
P op;
op.set(op.imap, E0::F0, 100);
op.set(op.imap, E0::F1, 101);
op.set(op.imap, E0::F2, 102);
cout << op.access_to_map(op.imap, E0::F1) << endl;
}
$ clang++ -std=c++11 test.cpp && ./a.out
101
But if I compile it with intel compiler (icpc version 15.0.3 (gcc version 5.1.0 compatibility)), the compiler gives me this error message (which I don't undertand at all, especially when clang will compile the code):
$ icpc -std=c++ test.cpp && ./a.out
test.cpp(67): error: no instance of function template "P::access_to_map" matches the argument list
argument types are: (std::__1::map<E0, int, std::__1::less<E0>, std::__1::allocator<std::__1::pair<const E0, int>>>, E0)
object type is: P
cout << op.access_to_map(op.imap, E0::F1) << endl;
And it also confuses me by not complaining about the set function.
Does anyone have any idea what is going on here?
Note: My answer applies to g++ - hopefully it's the same as icc.
Here is a smaller test case:
struct Base
{
int func(int t) { return t; }
};
struct Der : Base
{
template<typename T>
auto f(T t) -> decltype(Base::func(t))
{
return t;
}
};
int main(){ Der d; d.f(5); }
The error is:
mcv.cc: In function 'int main()':
mcv.cc:16:25: error: no matching function for call to 'Der::f(int)'
int main(){ Der d; d.f(5); }
^
mcv.cc:16:25: note: candidate is:
mcv.cc:9:7: note: template<class T> decltype (t->Base::func()) Der::f(T)
auto f(T t) -> decltype(Base::func(t))
^
mcv.cc:9:7: note: template argument deduction/substitution failed:
mcv.cc: In substitution of 'template<class T> decltype (t->Base::func()) Der::f(T) [with T = int]':
mcv.cc:16:25: required from here
mcv.cc:9:38: error: cannot call member function 'int Base::func(int)' without object
auto f(T t) -> decltype(Base::func(t))
This can be fixed by changing decltype(Base::func(t)) to decltype(this->Base::func(t)). A corresponding fix fixes your code sample, for me.
Apparently, the compiler doesn't consider that Base::func(t) should be called with *this as hidden argument. I don't know if this is a g++ bug, or if clang is going beyond the call of duty.
Note that in C++14, since the function has a single return statement, the trailing return type can be omitted entirely:
template<typename T>
auto f(T t)
{
return t;
}
I'm writing two functions with the same name (and with similar parameters):
The first one only takes a variable amount of integer parameters (minimum one).
The second one takes a variable amount of struct coordinate as parameters (again minimum one).
The struct coordinate can be constructed from both int and std::vector<int>, so the second one can in that way also take int and std::vector<int>.
The problem is that if the first parameter to the second function is an int then the compilation fails.
Is it possible to make the third line in main() call the other tmp function?
My current implementation is listed below.
int main(int argc, char** argv)
{
tmp(1, 2, 3, 4, 5); // OK! First function calls 'single'
tmp(std::vector<int>{1, 2, 3}, 4, 5); // OK! Second function calls 'multi'
tmp(1, std::vector<int>{2, 3}, 4); // Compilation error! First function calls 'single'
return 0;
}
Compiler output:
prog.cpp: In instantiation of ‘std::vector<int> tmp(int, types ...) [with types = {std::vector<int, std::allocator<int> >, int}]’:
prog.cpp:58:34: required from here
prog.cpp:29:23: error: no matching function for call to ‘single(std::vector<int>&, std::vector<int>&, int&)’
single(list, ints ...);
^
prog.cpp:29:23: note: candidates are:
prog.cpp:12:6: note: void single(std::vector<int>&, int)
void single(std::vector<int>& list, int first)
^
prog.cpp:12:6: note: candidate expects 2 arguments, 3 provided
prog.cpp:18:6: note: template<class ... types> void single(std::vector<int>&, int, types ...)
void single(std::vector<int>& list, int first, types ... ints)
^
prog.cpp:18:6: note: template argument deduction/substitution failed:
prog.cpp:29:23: note: cannot convert ‘ints#0’ (type ‘std::vector<int>’) to type ‘int’
single(list, ints ...);
^
My current implementation:
#include <iostream>
#include <vector>
struct coordinate
{
std::vector<int> c;
coordinate(std::vector<int> A) : c(A) {}
coordinate(int A) : c{A} {}
};
// Function to end the recursive call to single
void single(std::vector<int>& list, int first)
{
list.push_back(first);
}
// Recursive function to store the parameters (only int)
template <typename... types>
void single(std::vector<int>& list, int first, types ... ints)
{
list.push_back(first);
single(list, ints ...);
}
// 'First' function
template <typename... types>
std::vector<int> tmp(int i, types ... ints)
{
std::vector<int> list;
list.push_back(i);
single(list, ints ...);
return list;
}
// Function to end the recursive call to multi
void multi(std::vector<std::vector<int> >& list, coordinate first)
{
list.push_back(first.c);
}
// Recursive function for storing the parameters (only 'coordinate')
template <typename... types>
void multi(std::vector<std::vector<int> >& list, coordinate first, types ... coords)
{
list.push_back(first.c);
multi(list, coords ...);
}
// 'Second' function
template <typename... types>
std::vector<std::vector<int> > tmp(coordinate i, types ... coords)
{
std::vector<std::vector<int> > list;
list.push_back(i.c);
multi(list, coords ...);
return list;
}
Alright, based on my new understanding from the comment you want something like this:
template <typename... Args>
typename std::enable_if<all_ints<Args...>::value>::type
tmp(Args... args) {
// the all integer version
}
template <typename... Args>
typename std::enable_if<!all_ints<Args...>::value>::type
tmp(Args... args) {
// the coordinate version
}
Then you just need to write the type trait to check if everything is an integer.
template <typename... T>
struct all_ints : std::true_type { };
template <typename T, typename... Rest>
struct all_ints<T, Rest...>
: std::integral_constant<bool,
std::is_integral<T>::value && all_ints<Rest...>::value>
{ }
I used std::is_integal to handle all the integer types. If you really want explicitly int, you can fix it.