I'm having issues when writing template members of a non template class, specfically when deducing template parameters of a template template argument.
The following code, minimal, illustrates my problem. I suspect there are at least two different problems down there.
#include <iostream>
#include <list>
using namespace std;
struct A
{
list<int> l;
template<template<class> class Iterable>
A(Iterable<int>& it);
};
template<template<class> class Iterable>
A::A(Iterable<int>& it) : l(list<int>())
{
for(int i : it)
l.push_back(i);
}
int main()
{
list<int> l = {1, 2, 3, 4};
A a(l);
for(int i : a.l)
cout << i << " ";
cout << endl;
A b = {1, 2, 3, 4};
for(int i : b.l)
cout << i << " ";
cout << endl;
}
Remarks : I really want to provide the definition out of class. I also want the prototype of the constructor of A
to work with lists of ints, vectors of ints, as well as initializer list.
I suspect there are at least two different problems down there.
Correct: I see three different problems.
1) std::list
needs two template parameter; the second is defaulted (std::allocator<T>
, where T
is the first template parameter); and also vectors, deques, sets, etc. requires more that a template parameter (some defaulted).
So template<template<class> class>
doesn't match std::list
and other STL containers.
To be more generic, I suggest something as
template <template <typename...> class Iterable, typename ... Ts>
A (Iterable<int, Ts...> & it);
2) A b = {1, 2, 3, 4};
doesn't work because you're calling a constructor with four arguments and you have only constructors with one argument. So you have to explicit the container with something as
A b = std::list<int>{1, 2, 3, 4};
Given that you expect std::initializer_list
as default container, if you accept to double the graphs ({ { 1, 2, 3, 4 } }
, instead of { 1, 2, 3, 4 }
, to pass a single argument to the constructor) you can indicate std::initializer_list
(or another container, if you prefer) as default Iterable
.
I mean... if you declare the constructor as follows
template <template <typename...> class Iterable = std::initializer_list,
typename ... Ts>
A (Iterable<int, Ts...> const & it);
then you can initialize b
as
A b {{1, 2, 3, 4}};
3) the signature Iterable<int, Ts...> & it
, in your template constructor, accept a l-value reference, so accept
list<int> l = {1, 2, 3, 4};
A a(l);
but doesn't accept
A b = std::list<int>{1, 2, 3, 4};
because std::list<int>{1, 2, 3, 4}
is a r-value.
To solve this problem you can write a different constructor for r-value references but, in this case, I suspect that is acceptable simply modify the template construct to make it able to accept const l-value references
A (Iterable<int, Ts...> const & it);
// .....................^^^^^
Bonus (Off Topic) suggestion: if you're interested in accepting STL containers, you can simplify your template constructor using the l
constructor that accept a couple of iterators (begin()
, end()
), so
template <template <typename...> class Iterable, typename ... Ts>
A::A (Iterable<int, Ts...> const & it) : l{it.cbegin(), it.cend()}
{ }
or also
template <template <typename...> class Iterable, typename ... Ts>
A::A (Iterable<int, Ts...> const & it) : l{std::cbegin(it), std::cend(it)}
{ }
The following is your code modified
#include <initializer_list>
#include <iostream>
#include <list>
struct A
{
std::list<int> l;
template <template <typename...> class Iterable = std::initializer_list,
typename ... Ts>
A (Iterable<int, Ts...> const & it);
};
template <template <typename...> class Iterable, typename ... Ts>
A::A (Iterable<int, Ts...> const & it) : l{std::cbegin(it), std::cend(it)}
{ }
int main ()
{
std::list<int> l {1, 2, 3, 4};
A a(l);
for (auto i : a.l)
std::cout << i << " ";
std::cout << std::endl;
A b {{1, 2, 3, 4}};
for (auto i : b.l)
std::cout << i << " ";
std::cout << std::endl;
}