Search code examples
c++c++17template-argument-deduction

Why can't the compiler deduce the template parameter when used with a conversion operator?


Consider the following code:

#include <utility>

template<typename T>
struct wrapper {
    T value;
};

struct foo {
    operator wrapper<int>() {
        return{10};
    }
};


int main() {
    foo f;
    wrapper w = f; // error
    std::pair p = std::make_pair(1, 0); // ok
}

gcc 7.1.1 fails to compile at the marked line above:

main.cpp: In function 'int main()':
main.cpp:17:17: error: class template argument deduction failed:
     wrapper w = f; // error
                 ^
main.cpp:17:17: error: no matching function for call to 'wrapper(foo&)'
main.cpp:4:8: note: candidate: template<class T> wrapper(wrapper<T>)-> wrapper<T>
 struct wrapper {
        ^~~~~~~
main.cpp:4:8: note:   template argument deduction/substitution failed:
main.cpp:17:17: note:   'foo' is not derived from 'wrapper<T>'
     wrapper w = f; // error
                 ^

f is convertible to wrapper<int>, so I expect that to happen. From there the compiler should be able to deduce that T is int. But it can't.

The compiler can deduce std::pair's template parameter correctly, so I'm wondering why this isn't the case with the wrapper.

Any ideas?


Solution

  • For class template argument deduction, the "overload set" is composed as described in [over.match.class.deduct/1]. Those are the following:

    A set of functions and function templates is formed comprising:
    (1.1) - 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:
    (1.1.1) - 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.
    (1.1.2) - The types of the function parameters are those of the constructor.
    (1.1.3) - 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.

    (1.2) - If the primary class template C is not defined or does not declare any constructors, an additional function template derived as above from a hypothetical constructor C().

    (1.3) - An additional function template derived as above from a hypothetical constructor C(C), called the copy deduction candidate.

    (1.4) - For each deduction-guide, a function or function template with the following properties:
    (1.4.1) - The template parameters, if any, and function parameters are those of the deduction-guide.
    (1.4.2) - The return type is the simple-template-id of the deduction-guide.

    As you can see, the matching "function" in 1.1 only attempts to match the argument types to the template parameter types exactly. It doesn't take conversion into account (much like most other template deduction related behavior).

    The reason it works for std::pair is due to item 1.3, and the "copy deduction candidate" it defines.