14

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]

W.F.
  • 13,888
  • 2
  • 34
  • 81

2 Answers2

11

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.

StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
1

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() {}
};
Martin Fehrs
  • 792
  • 4
  • 13
  • Reference? The ordering rules are quite clear if this was a free function call. – StoryTeller - Unslander Monica Sep 05 '17 at 12:13
  • I added the reference to the answer – Martin Fehrs Sep 05 '17 at 12:14
  • 3
    No, I meant as in a standard reference. I read Myers' book and this is not covered by it. You are conflating different things here. – StoryTeller - Unslander Monica Sep 05 '17 at 12:15
  • I do not have the standard reference at hand. I have to search for it. But Scott Meyers is talking about it. In his examples he is not using template template arguments but this should not change the order. If I rembember correctly, It is because of the universal reference (forwarding reference) used in the template constructor. – Martin Fehrs Sep 05 '17 at 12:23
  • The tempalte parameter T is not part of the function signature. It is part of the class. A Free function call is not exactly the same. – Martin Fehrs Sep 05 '17 at 12:27
  • @MartinKalbfuß I think I know what you're referring to. These are not forwarding reference, these are plain rvalue references. This has not to do with the technique of having a constructor that takes a forwarding reference and enable it if not itself. – Guillaume Racicot Sep 05 '17 at 12:29
  • I think the first one is a forwarding reference even if it looks like a rvalue reference, because the argument is not a concrete type. The second one is a rvalue reference because T is part of the class signature. – Martin Fehrs Sep 05 '17 at 12:33
  • 1
    although this is not the direct answer it provides nice workaround, thanks! – W.F. Sep 05 '17 at 12:41
  • I concur with @W.F. on that +1 – StoryTeller - Unslander Monica Sep 05 '17 at 12:42
  • 3
    @MartinKalbfuß For the nth time, there is no perfect forwarding in this example. There are no forwarding references anywhere. – Barry Sep 05 '17 at 21:44