I wanted to create a simple auxiliary algorithm that would fill a container, such as std::vector<T>
, with a geometric progression (the first term is a
, and the n
-th term is given by a * pow(r, n-1)
, where r
is a given ratio); I created the following code:
#include<vector>
#include<algorithm>
#include<iostream>
template<template <typename> class Container, typename T>
void progression(Container<T>& container, T a, T ratio, size_t N) {
if(N > 0) {
T factor = T(1);
for(size_t k=0; k<N; k++) {
container.push_back(a * factor);
factor *= ratio;
}
}
}
int main() {
std::vector<double> r;
progression(r, 10.0, 0.8, static_cast<size_t>(10));
for(auto item : r) {
std::cout<<item<<std::endl;
}
return 0;
}
which yields the following errors upon attempting compilation:
$ g++ geometric.cpp -std=c++11 # GCC 4.7.2 on OS X 10.7.4
geometric.cpp: In function ‘int main()’:
geometric.cpp:18:52: error: no matching function for call to ‘progression(std::vector<double>&, double, double, size_t)’
geometric.cpp:18:52: note: candidate is:
geometric.cpp:6:6: note: template<template<class> class Container, class T> void progression(Container<T>&, T, T, size_t)
geometric.cpp:6:6: note: template argument deduction/substitution failed:
geometric.cpp:18:52: error: wrong number of template arguments (2, should be 1)
geometric.cpp:5:36: error: provided for ‘template<class> class Container’
Clang's error message is more subtle:
$ clang++ geometric.cpp -std=c++11 # clang 3.2 on OS X 10.7.4
geometric.cpp:18:3: error: no matching function for call to 'progression'
progression(r, 10, 0.8, 10);
^~~~~~~~~~~
geometric.cpp:6:6: note: candidate template ignored: failed template argument deduction
void progression(Container<T>& container, T a, T ratio, size_t N) {
^
1 error generated.
I would have expected that using template template parameters I would be able to deduce not only the container, but also the container's value_type
(T
in this case).
So, the question is: how can I create a generic function which will be able to deduce both the container type and the value type?
I am sure I'm missing something obvious - I appreciate your patience and help.
The following code behaves as expected:
#include<vector>
#include<algorithm>
#include<iostream>
template<template <typename...> class Container, typename T, typename... Args>
void progression(Container<Args...>& container, T a, T ratio, size_t N) {
if(N > 0) {
T factor = T(1);
for(size_t k=0; k<N; k++) {
container.push_back(a * factor);
factor *= ratio;
}
}
}
int main() {
std::vector<double> r;
progression(r, 10.0, 0.8, 10);
for(auto item : r) {
std::cout<<item<<std::endl;
}
return 0;
}
Output:
10
8
6.4
5.12
4.096
3.2768
2.62144
2.09715
1.67772
1.34218
The first problem is, that you are forgetting that std::vector<>
is a class template accepting two template parameters (the element type and the allocator), not one. The fact that the second template parameter has a default value is irrelevant when you are using template template parameters:
template<template <typename, typename> class Container, typename T, typename A>
// ^^^^^^^^ ^^^^^^^^^^
void progression(Container<T, A>& container, T a, T ratio, size_t N) {
// ^^^^
// ...
}
Notice, that this will make it impossible to pass, for instance, an instance of std::map
or std::unordered_map
as the first function argument. Therefore, my suggestion is to give up deducing that the first argument is the instance of a standard container (standard containers are just not that uniform):
template<typename C, typename T, typename A>
// ^^^^^^^^^
void progression(C& container, T a, T ratio, size_t N) {
// ^^
// ...
}
What you may want to do then is to express the compile-time constrain, perhaps through a static_assert
and based on a custom type trait, that C
must be an instance of a standard container.
Alternatively, you can use variadic templates as suggested by KerrekSB in his answer (but that still won't prevent you from passing in an instance of any other kind of template, even non-container ones).
The second problem is in the way you are calling your template:
progression(r, 10, 0.8, 10);
Here, the type of the second argument is int
, while the type of the container element is double
. This will confuse the compiler when performing type deduction. Either call it this way:
progression(r, 10.0, 0.8, 10);
Or allow your compiler to deduce a different type for the second argument (possible SFINAE-constraining it to be something that can convert to the element type).