0

I want write an simple demo, but it compile failed, I can't find the reason.

#include <iostream>
#include <vector>
#include <list>
using namespace std;


template <
    typename T,  
    template <typename W> typename  Container = std::vector 
>
class myclass
{
public:
    Container<T> myc;

public:
    void func();
    myclass() 
    {
        for (int i = 0; i < 10; ++i)
        {
            myc.push_back(i);  
        }
    }
};

template <
    typename T,
    template <typename W> typename  Container 
>
void myclass<T, Container>::func()
{
    cout << "good!" << endl;
}


int main()
{
   
    myclass<int, vector> mylistobj2; 
    mylistobj2.func();


    return 0;
}

the compile reason is that

tmp.cpp: In function ‘int main()’:
tmp.cpp:100:30: error: type/value mismatch at argument 2 in template parameter list for ‘template<class T, template<class W> class Container> class _nmsp1::myclass’
  100 |  _nmsp1::myclass<double, list> mylistobj2; 
      |                              ^
tmp.cpp:100:30: note:   expected a template of type ‘template<class W> class Container’, got ‘template<class _Tp, class _Alloc> class std::__cxx11::list’
tmp.cpp:101:13: error: request for member ‘func’ in ‘mylistobj2’, which is of non-class type ‘int’
  101 |  mylistobj2.func();
kiner_shah
  • 3,939
  • 7
  • 23
  • 37
  • 1
    [The `std::vector` template](https://en.cppreference.com/w/cpp/container/vector) doesn't take a single template argument. Neither does [`std::list`](https://en.cppreference.com/w/cpp/container/list). Instead of using template-template arguments, use plain template arguments, like plain `typename Container`. Then use the proper type when using your template, like `myclass>`. Use `Container::value_type` if you need the type stored in the container (instead of your `T`). – Some programmer dude Feb 01 '22 at 07:24
  • 2
    And a note about writing good questions: Make sure that the error you get actually matches the code you show! Yours doesn't. Create your [mre], then copy-paste the errors from that example. – Some programmer dude Feb 01 '22 at 07:29
  • Compile with `-std=c++17` and it will work. – Passer By Feb 01 '22 at 07:29
  • @PasserBy: not with clang though [Demo](https://godbolt.org/z/odsfbWv4e). – Jarod42 Feb 01 '22 at 09:56

2 Answers2

1

std::vector has two template parameters (the 2nd one has default argument), while the template template parameter Container is declared with only one template parameter. They don't match. You may apply parameter pack.

template <
    typename T,  
    template <typename...> typename  Container = std::vector 
>
class myclass
{
public:
    Container<T> myc;

public:
    void func();
    myclass() 
    {
        for (int i = 0; i < 10; ++i)
        {
            myc.push_back(i);  
        }
    }
};

template <
    typename T,
    template <typename...> typename  Container 
>
void myclass<T, Container>::func()
{
    cout << "good!" << endl;
}

Your code would work fine with C++17; since C++17 (CWG 150), the default template arguments are allowed for a template template argument to match a template template parameter with fewer template parameters.

songyuanyao
  • 169,198
  • 16
  • 310
  • 405
0

The problem is that prior to C++17

a template template argument had to be a template with parameters that exactly match the parameters of the template template parameter it substitutes with some exceptions related to variadic template parameters.

In your given example this means that since std::vector template of the standard library has more than one parameter: The second parameter (which describes an allocator) has a default value, but prior to C++17 this was not considered when matching std::vector to the Container parameter.

There are 2 ways to solve this.

Solution 1

One solution is to rewrite the class myclass declaration so that the Container parameter expects containers with two template parameters as shown below:

template <typename T,  
    template <typename W,typename Alloc = std::allocator<W>> typename  Container = std::vector> //note the second template parameter Alloc added here 

class myclass
{
public:
    Container<T> myc;

public:
    void func();
    myclass() 
    {
        for (int i = 0; i < 10; ++i)
        {
            myc.push_back(i);  
        }
    }
};

template <typename T,
    template <typename W, typename Alloc> typename  Container> //note the second template parameter here

void myclass<T, Container>::func()
{
    cout << "good!" << endl;
}

The output of the above modified program can be seen here.

Solution 2

You can make use of parameter pack to solve this problem.

template <typename T,  
    template <typename...> typename  Container = std::vector> //note we have parameter pack

class myclass
{
public:
    Container<T> myc;

public:
    void func();
    myclass() 
    {
        for (int i = 0; i < 10; ++i)
        {
            myc.push_back(i);  
        }
    }
};

template <typename T,
    template <typename...> typename  Container> //note we have parameter pack 

void myclass<T, Container>::func()
{
    cout << "good!" << endl;
}

The output of the above modified program can be seen here.

Note also that your program works without any modification with C++17.

Jason
  • 36,170
  • 5
  • 26
  • 60