jeudi 30 novembre 2023

Template specialisation or function overload

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

Aucun commentaire:

Enregistrer un commentaire