Search code examples
c++c++11templatesvariadic-templatestemplate-templates

Deduction error of template parameters of a template template argument


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.


Solution

  • 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;
     }