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
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 asstd::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 anstd::initializer_list<MyClass>That's why overload should be preferred over specialization in general case.
There are no undefined behavior. overload is completly legal.