1

I do not understand why in the following code, the shared_ptr<Derived<int>> isn't implicitly converted to a shared_ptr<Base<int>>:

#include <memory>
    
template <typename T>
class Base {
};
    
template <typename T>
class Derived : public Base<T> {
};
    
template <typename T>
T foo(std::shared_ptr<Base<T>>) {
    return T{};
}
    
void main() {
    foo(std::make_shared<Base<int>>());
    foo(std::make_shared<Derived<int>>());
}

I came across convert std::shared_ptr<Derived> to const shared_ptr<Base>&, which seems related to me. Am I getting an error because I made a template of the function?

The error I get is:

E0304 no instance of function template "foo" matches the argument list

C2664 'std::shared_ptr<Base<int>> foo<int>(std::shared_ptr<Base<int>>)': cannot convert argument 1 from 'std::shared_ptr<Derived<int>>' to 'std::shared_ptr<Base<int>>'

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
user
  • 934
  • 6
  • 17
  • 1
    Can you please create a [mcve] and include that in your question? Compile that and include the exact error message it produces. – Ulrich Eckhardt Oct 21 '20 at 17:30
  • Yes, it's because of a template. – HolyBlackCat Oct 21 '20 at 17:32
  • 3
    @Eljay It's implicitly convertible though. – HolyBlackCat Oct 21 '20 at 17:34
  • 2
    @UlrichEckhardt I am not sure what more you want, i posted the entire code and the only error message i had from the compiler – user Oct 21 '20 at 17:35
  • 1
    @user this code is not compilable for other reasons. This is not minimal reproducible example'. – SergeyA Oct 21 '20 at 17:37
  • Does your compiler really refer to `Derived` as a type when it is actually a template? I.e. is it `shared_ptr` or is it `shared_ptr>`? – Ulrich Eckhardt Oct 21 '20 at 17:39
  • I took the liberty of fixing unrelated typos, but please try and post the *exact* code you're compiling. `ctrl-c` `ctrl-v` is your friend. – cigien Oct 21 '20 at 17:40
  • @UlrichEckhardt it is `shared_ptr>` returned from the `make_shared>()` – user Oct 21 '20 at 17:47
  • I was referring to the error message. You know, that's why a [mcve] is required. That's why you should copy'n'paste the output. That's why I'm voting to close your question as off-topic currently. – Ulrich Eckhardt Oct 21 '20 at 18:45
  • @UlrichEckhardt I 've edited the question with copy pasted compiler messages, i didn't realize those would help. Are these what you were referring to ? – user Oct 21 '20 at 19:53
  • 1
    @user you need to use `<` when quoting things that have `<...>` pairs in them, so they are not confused with HTML markup. I have edited the error messages for you so the `<` show correctly. – Remy Lebeau Oct 21 '20 at 20:05

1 Answers1

3

The reason for this behavior is the fact that foo is a template. Note, that everything works correctly if foo is not a template:

int foo(std::shared_ptr<Base<int>>) {
    return int{};
}

However, when foo is a template, the compiler first needs to instantiate foo, and it wants to be able to instantiate an exact match, since implicit conversions do not apply at this point. And this instantiation can not succeed, thus the error.

One way to workaround this issue would be to make foo a very greedy template, and then add additional constraints of convertibility. For example:

#include <memory>

template <typename T>
class Base {
public:
    using Type = T;
};

template <typename T>
class Derived : public Base<T> {
};

template <typename T>
auto foo(T) -> std::enable_if_t<std::is_convertible_v<T, std::shared_ptr<Base<typename T::element_type::Type>>>, typename T::element_type>
{    
    return typename T::element_type{};
}


int main() {
    foo(std::make_shared<Derived<int>>()); //OK
    foo(20); // error
}

Note, that I have added a Type member to the base class. This is not strictly necessary, but simplifies the code.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
SergeyA
  • 61,605
  • 5
  • 78
  • 137
  • 1
    One thing you can do to allow the implicit conversion to take place is call `foo(std::make_shared>());`, which will allow the implicit conversion to be useful. – Nathan Pierson Oct 21 '20 at 17:37
  • Ok, but the template has already been instanciated with the first call in `main` and yet you are saying the call `foo(std::make_shared>())` will not compile because it will rather find an exact match than take the instanciation created from `foo(std::make_shared>());` ? – user Oct 21 '20 at 17:52
  • @user templates are instantiated independently for each call to the template. If exact much has already been instantiated, it would be used (i.e. no new definition would be produced). The fact that template was instantiated for some code before doesn't mean that it would be blindly used - the process will be the same for the next attempt. – SergeyA Oct 21 '20 at 17:55
  • @SergeyA ok that makes sense, it feels stupid but this is actually what i couldn't figure out, thanks – user Oct 21 '20 at 17:56