Can constructor template cause ambiguity in the c++17 parameter deduction of class template

453 views Asked by At

Consider a simple example:

template <class T>
struct foo {
    template <template <class> class TT>
    foo(TT<T>&&) {}
    foo(foo<T>&&){}
    foo() {}
};

int main() {
    foo      f1(foo<int>{}); //case 1.
    foo<int> f2(foo<int>{}); //case 2.
}

Case 1. causes ambiguity in the template argument deduction of foo class in clang but not in gcc. I thought that template functions (here - constructor) have lower priority in overload resolution. Is it not the case here?

Error message:

prog.cc:10:14: error: ambiguous deduction for template arguments of 'foo'
    foo      f1(foo<int>{}); //case 1.
             ^
prog.cc:4:5: note: candidate function [with T = int, TT = foo]
    foo(TT<T>&&) {}
    ^
prog.cc:5:5: note: candidate function [with T = int]
    foo(foo<T>&&){}
    ^
1 error generated.

[clang demo] [gcc demo]

2

There are 2 answers

10
StoryTeller - Unslander Monica On BEST ANSWER

This is a Clang bug. The fact the candidate set is formed from c'tors should be immaterial, since after the candidate set is formed the best overload is chosen using the same rules for ordering implicit conversion sequences and template function ordering.

To quote [over.match.funcs]/1:

The subclauses of [over.match.funcs] describe the set of candidate functions and the argument list submitted to overload resolution in each of the seven contexts in which overload resolution is used. The source transformations and constructions defined in these subclauses are only for the purpose of describing the overload resolution process. An implementation is not required to use such transformations and constructions.

This clearly states that the overload resolution process is the same always. The only difference is how the candidate set is formed.

And as specified by [over.match.class.deduct]/1

A set of functions and function templates is formed comprising:

  • For each constructor of the primary class template designated by the template-name, if the template is defined, a function template with the following properties:

    • The template parameters are the template parameters of the class template followed by the template parameters (including default template arguments) of the constructor, if any.

    • The types of the function parameters are those of the constructor.

    • The return type is the class template specialization designated by the template-name and template arguments corresponding to the template parameters obtained from the class template.

Each c'tor will introduce a pseudo function into the candidate set. Like this:

template <class T>                           foo(foo<T>&&) -> foo<T> 
template <class T, template<class> class TT> foo(TT<T>&&) -> foo<T> 

To illustrate further, if this was a free function bar:

template <template <class> class TT, class T>
void bar(TT<T>&&) {}

template <class T>
void bar(foo<T>&&){}

Then template function ordering would place the first overload lower than the second.

10
Martin Fehrs On

They do not have lower priority. You can solve the problem using SFINEA. This is described in Effective Modern C++ from Scott Meyers.

template <class T>
struct foo {
    template <template <class> class TT, class =  std::enable_if_t<!std::is_same_v<foo<T>, std::decay_t<TT<T>>>>>
    foo(TT<T>&&) {}
    foo(foo<T>&&){}
    foo() {}
};