glades
glades

Reputation: 4737

CTAD doesn't work with defaulted template arguments?

Compare the following case when I have a class object that takes a vector. The non-deduced parameter T can be substituted fine with the default template argument:

#include <vector>

template <typename T = int>
struct container
{
    container(std::vector<T> vec) {}
};

int main()
{
    container C = std::vector{1,2,3,4,5};
}

This is not the case for my class which is a bit more complicated (CompilerExplorer):

#include <cstdio>
#include <initializer_list>
#include <variant>

template <size_t> struct obj;

template<size_t Vs>
using val = std::variant<std::monostate, int, struct obj<Vs>>;

template <size_t Vs = 0>
struct obj
{
    obj() = default;
    obj(std::initializer_list<val<Vs>> init) {
        printf("initializer of object called, Vs = %d\n", Vs);
    }
};


template <size_t Vs = 0>
struct container : public obj<Vs>
{
    container(obj<0> init) {}
};


int main()
{
    container<5> some_container = obj{1,2,5,2,obj{1,2,33},2,2};
}

This fails with the following error:

<source>: In function 'int main()':
<source>:29:57: error: class template argument deduction failed:
   29 |     container<5> some_container = obj{1,2,5,2,obj{1,2,33},2,2};
      |                                                         ^
<source>:29:57: error: no matching function for call to 'obj(int, int, int)'
<source>:14:5: note: candidate: 'template<long unsigned int Vs> obj(std::initializer_list<std::variant<std::monostate, int, obj<Vs> > >)-> obj<<anonymous> >'
   14 |     obj(std::initializer_list<val<Vs>> init) {
      |     ^~~

But it works when I supplement the template specialization obj<0> in the instantiation of the container (in main). Any ideas why this doesn't work for my class and how I can fix it? I don't want to force the user to specify the template each time.

Upvotes: 0

Views: 336

Answers (1)

perivesta
perivesta

Reputation: 4021

This problem already exists in the simpler case of just

auto o = obj{1,2,33};

which yields this error:

<source>:29:24: error: class template argument deduction failed:
   29 |     auto o = obj{1,2,33};
      |                        ^
<source>:29:24: error: no matching function for call to 'obj(int, int, int)'
<source>:14:5: note: candidate: 'template<long unsigned int Vs> obj(std::initializer_list<std::variant<std::monostate, int, obj<Vs> > >)-> obj<<anonymous> >'
   14 |     obj(std::initializer_list<val<Vs>> init) {
      |     ^~~
<source>:14:5: note:   template argument deduction/substitution failed:
<source>:29:24: note:   mismatched types 'std::initializer_list<std::variant<std::monostate, int, obj<Vs> > >' and 'int'
   29 |     auto o = obj{1,2,33};

So, the compiler is unable to deduce, that the three ints should be an initializer list. If you add extra braces around them, the compiler recognizes that this should actually be a single list argument instead of three separate ones and it works:

auto o = obj{{1,2,33}};

This also carries over to the more complicated case:

container some_container = obj{{1,2,5,2,obj{{1,2,33}},2,2}};

Upvotes: 0

Related Questions