Template specialisation or function overload

97 Views Asked by At

I created a two classes - one can be converted to another by the conversion operator:

struct MyClass{};

struct MyClass2{
    operator MyClass() const { return MyClass{}; }
};

and a specialised template function (specialisation for std::initializer_list<MyClass>):

template<typename T>
void foo(std::initializer_list<T>)
{
}

template<>
void foo(std::initializer_list<MyClass>)
{
    std::cout << "specialised template foo<>()";
}

When I try to call foo with the initializer list mixing MyClass and MyClass2:

foo({MyClass{}, MyClass2{}, MyClass2{}});

compiler opposes (as I am mixing two different types):

<source>:35:8: error: no matching function for call to 'foo(<brace-enclosed initializer list>)'
   35 |     foo({MyClass{}, MyClass2{}, MyClass2{}});
      |     ~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
<source>:19:6: note: candidate: 'template<class T> void foo(std::initializer_list<_Tp>)'
   19 | void foo(std::initializer_list<T>)
      |      ^~~
<source>:19:6: note:   template argument deduction/substitution failed:
<source>:35:8: note:   deduced conflicting types for parameter '_Tp' ('MyClass' and 'MyClass2')
   35 |     foo({MyClass{}, MyClass2{}, MyClass2{}});
      |     ~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

But from some reason if I add an non-template overload of foo for the same type as in template specialisation - it works like a charm!

void foo(std::initializer_list<MyClass>)
{
    std::cout << “overloaded foo()";
}

int main(int argc, char **argv) {
    foo({MyClass{}, MyClass2{}, MyClass2{}}); // Program stdout : overloaded foo()
}

I guess that non-template overload has a priority over templates when compiler looks up for a function. But why it does work with overload and does not work with template specialisation? Is it undefined behaviour? Or is it completely legal to do it so?

Live example: https://godbolt.org/z/5ohhK1jeb

1

There are 1 best solutions below

0
Jarod42 On BEST ANSWER

why it does work with overload and does not work with template specialisation?

  • specialization doesn't participe to overload selection, only the primary template does. (After primary template is selected, specialization are "used" for definition).

  • template deduction "ignores" type conversions.

  • {..} has no types, and can only be deduced in few context. As its types are not all the same, {..} cannot be deduced as std::initializer_list<T>.

With overload void foo(std::initializer_list<MyClass>), no deduction occur, and might check if we can match argument to this function, and the provided {MyClass{}, MyClass2{}, MyClass2{}} can indeed be an std::initializer_list<MyClass>

That's why overload should be preferred over specialization in general case.

Is it undefined behaviour? Or is it completely legal to do it so?

There are no undefined behavior. overload is completly legal.