How do `shared_ptr`s achieve covariance? - c++

It is possible to copy or construct a shared_ptr<Base> from shared_ptr<Deriver> (i.e. shared_ptr<Base> ptr = make_shared<Derived>()). But as we all know, template classes are not convertible to each other, even if the template arguments are. So how can shared_ptrs check if the value of their pointers are convertible and do the conversion if they are?

Yes, specializations of the same class template by default have almost no relationship and are essentially treated like unrelated types. But you can always define implicit conversions between class types by defining converting constructors (To::To(const From&)) and/or conversion functions (From::operator To() const).
So what std::shared_ptr does is define template converting constructors:
namespace std {
template <class T>
class shared_ptr {
public:
template <class Y>
shared_ptr(const shared_ptr<Y>&);
template <class Y>
shared_ptr(shared_ptr<Y>&&);
// ...
};
}
Though the declaration as shown would allow conversions from any shared_ptr to any other, not just when the template argument types are compatible. But the Standard also says about these constructors ([util.smartptr]/5 and [util.smartptr.const]/18 and util.smartptr.const]/21):
For the purposes of subclause [util.smartptr], a pointer type Y* is said to be compatible with a pointer type T* when either Y* is convertible to T* or Y is U[N] and T is cv U[].
The [...] constructor shall not participate in overload resolution unless Y* is compatible with T*.
Although this restriction could be done in any way, including compiler-specific features, most implementations will enforce the restriction using an SFINAE technique (Substitution Failure Is Not An Error). One possible implementation:
#include <cstddef>
#include <type_traits>
namespace std {
template <class Y, class T>
struct __smartptr_compatible
: is_convertible<Y*, T*> {};
template <class U, class V, size_t N>
struct __smartptr_compatible<U[N], V[]>
: bool_constant<is_same_v<remove_cv_t<U>, remove_cv_t<V>> &&
is_convertible_v<U*, V*>> {};
template <class T>
class shared_ptr {
public:
template <class Y, class = enable_if_t<__smartptr_compatible<Y, T>::value>>
shared_ptr(const shared_ptr<Y>&);
template <class Y, class = enable_if_t<__smartptr_compatible<Y, T>::value>>
shared_ptr(shared_ptr<Y>&&);
// ...
};
}
Here the helper template __smartptr_compatible<Y, T> acts as a "trait": it has a static constexpr member value which is true when the types are compatible as defined, or false otherwise. Then std::enable_if is a trait which has a member type called type when its first template argument is true, or does not have a member named type when its first template argument is false, making the type alias std::enable_if_t invalid.
So if template type deduction for either constructor deduces the type Y so that Y* is not compatible with T*, substituting that Y into the enable_if_t default template argument is invalid. Since that happens while substituting a deduced template argument, the effect is just to remove the entire function template from consideration for overload resolution. Sometimes an SFINAE technique is used to force selecting a different overload instead, or as here (most of the time), it can just make the user's code fail to compile. Though in the case of the compile error, it will help that a message appears somewhere in the output that the template was invalid, rather than some error even deeper within internal template code. (Also, an SFINAE setup like this makes it possible for a different template to use its own SFINAE technique to test whether or not a certain template specialization, type-dependent expression, etc. is or isn't valid.)

It works because shared_ptr has (among others) a templated constructor
template<typename U> shared_ptr(U * ptr);
If U* is not convertible to the shared_ptr's contained type then you will get an error buried somewhere in the shared_ptr implementation.

Related

Overloading operator+() in global scope, for a template class inside a template class [duplicate]

I've stumbled over "Why is the template argument deduction not working here?" recently and the answers can be summed up to "It's a nondeduced context".
Specifically, the first one says it's such a thing and then redirects to the standard for "details", while the second one quotes the standard, which is cryptic to say the least.
Can someone please explain to mere mortals, like myself, what a nondeduced context is, when does it occur, and why does it occur?
Deduction refers to the process of determining the type of a template parameter from a given argument. It applies to function templates, auto, and a few other cases (e.g. partial specialization). For example, consider:
template <typename T> void f(std::vector<T>);
Now if you say f(x), where you declared std::vector<int> x;, then T is deduced as int, and you get the specialization f<int>.
In order for deduction to work, the template parameter type that is to be deduced has to appear in a deducible context. In this example, the function parameter of f is such a deducible context. That is, an argument in the function call expression allows us to determine what the template parameter T should be in order for the call expression to be valid.
However, there are also non-deduced contexts, where no deduction is possible. The canonical example is "a template parameter that appears to the left of a :::
template <typename> struct Foo;
template <typename T> void g(typename Foo<T>::type);
In this function template, the T in the function parameter list is in a non-deduced context. Thus you cannot say g(x) and deduce T. The reason for this is that there is no "backwards correspondence" between arbitrary types and members Foo<T>::type. For example, you could have specializations:
template <> struct Foo<int> { using type = double; };
template <> struct Foo<char> { using type = double; };
template <> struct Foo<float> { using type = bool; };
template <> struct Foo<long> { int type = 10; };
template <> struct Foo<unsigned> { };
If you call g(double{}) there are two possible answers for T, and if you call g(int{}) there is no answer. In general, there is no relationship between class template parameters and class members, so you cannot perform any sensible argument deduction.
Occasionally it is useful to inhibit argument deduction explicitly. This is for example the case for std::forward. Another example is when you have conversions from Foo<U> to Foo<T>, say, or other conversions (think std::string and char const *). Now suppose you have a free function:
template <typename T> bool binary_function(Foo<T> lhs, Foo<T> rhs);
If you call binary_function(t, u), then the deduction may be ambiguous and thus fail. But it is reasonable to deduce only one argument and not deduce the other, thus permitting implicit conversions. Now an explicitly non-deduced context is needed, for example like this:
template <typename T>
struct type_identity {
using type = T;
};
template <typename T>
bool binary_function(Foo<T> lhs, typename type_identity<Foo<T>>::type rhs)
{
return binary_function(lhs, rhs);
}
(You may have experienced such deduction problems with something like std::min(1U, 2L).)

Template argument deduction from inherited type

I want a setup like the following:
template <typename T> class a {};
class b : public a<int> {};
template <typename T>
void do_foo(std::unique_ptr<a<T>> foo)
{
// Do something with foo
}
int main()
{
do_foo(std::make_unique<b>());
}
This fails to compile with a note saying template argument deduction/substitution failed and mismatched types 'a<T>' and 'b'. It's pretty self-explanatory. I can help the compiler along by writing do_foo<int>(std::make_unique<b>());, but then I'm repeating myself by writing int twice.
Is there a way to get the compiler to deduce the template parameter in this case? And what would you call this behaviour? I tried searching for things like "template type deduction for inherited type", "polymorphic template deduction" etc.
Is there a way to get the compiler to deduce the template parameter in this case?
No. Not in C++14 (or even C++20).
And what would you call this behaviour?
Standard compliant. To be specific, this paragraph applies:
[temp.deduct.call]
4 In general, the deduction process attempts to find template
argument values that will make the deduced A identical to A (after
the type A is transformed as described above). However, there are
three cases that allow a difference:
If the original P is a reference type, the deduced A (i.e., the type referred to by the reference) can be more cv-qualified than the
transformed A.
The transformed A can be another pointer or pointer to member type that can be converted to the deduced A via a qualification
conversion ([conv.qual]).
If P is a class and P has the form simple-template-id, then the transformed A can be a derived class of the deduced A.
Likewise, if P is a pointer to a class of the form
simple-template-id, the transformed A can be a pointer to a derived class pointed to by the deduced A.
This is an exhaustive list of cases where a template argument can be validly deduced from a function argument even if it doesn't match the pattern of the function parameter exactly. The first and second bullets deal with things like
template<class A1> void func(A1&){}
template<class A2> void func(A2*){}
int main() {
const int i = 1;
func(i); // A1 = const int
func(&i); // A2 = const int
}
The third bullet is the one that is closest to our case. A class derived from a template specialization can be used to deduce a template parameter pertaining to its base. Why doesn't it work in your case? Because the function template parameter is unique_ptr<a<T>> and the argument you call it with is unique_ptr<b>. The unique_ptr specializations are not themselves related by inheritance. So they don't match the bullet, and deduction fails.
But it doesn't mean that a wrapper like unique_ptr prevents template argument deduction entirely. For instance:
template <typename> struct A {};
struct B : A<int> {};
template<typename> struct wrapper{};
template<> struct wrapper<B> : wrapper<A<int>> {};
template<typename T>
void do_smth(wrapper<A<T>>) {}
int main() {
do_smth(wrapper<B>{});
}
In this case, wrapper<B> derives from wrapper<A<int>>. So the third bullet is applicable. And by the complex (and recursive) process of template argument deduction, it allows B to match A<T> and deduce T = int.
TL;DR: unique_ptr<T> specializations cannot replicate the behavior of raw pointers. They don't inherit from the specializations of unique_ptr over T's bases. Maybe if reflection ever comes to C++, we'll be able to meta-program a smart pointer that does behave that way.
As workaround, you might add overload:
template <typename T>
void do_foo_impl(a<T>* foo)
{
return do_foo(std::unique_ptr<a<T>>(foo));
}
template <typename T>
auto do_foo(std::unique_ptr<T> foo) -> decltype(do_foo_impl(foo.release()))
{
do_foo_impl(foo.release());
}
Demo

C++ - Template parameter deduction failed from subclass [duplicate]

I've stumbled over "Why is the template argument deduction not working here?" recently and the answers can be summed up to "It's a nondeduced context".
Specifically, the first one says it's such a thing and then redirects to the standard for "details", while the second one quotes the standard, which is cryptic to say the least.
Can someone please explain to mere mortals, like myself, what a nondeduced context is, when does it occur, and why does it occur?
Deduction refers to the process of determining the type of a template parameter from a given argument. It applies to function templates, auto, and a few other cases (e.g. partial specialization). For example, consider:
template <typename T> void f(std::vector<T>);
Now if you say f(x), where you declared std::vector<int> x;, then T is deduced as int, and you get the specialization f<int>.
In order for deduction to work, the template parameter type that is to be deduced has to appear in a deducible context. In this example, the function parameter of f is such a deducible context. That is, an argument in the function call expression allows us to determine what the template parameter T should be in order for the call expression to be valid.
However, there are also non-deduced contexts, where no deduction is possible. The canonical example is "a template parameter that appears to the left of a :::
template <typename> struct Foo;
template <typename T> void g(typename Foo<T>::type);
In this function template, the T in the function parameter list is in a non-deduced context. Thus you cannot say g(x) and deduce T. The reason for this is that there is no "backwards correspondence" between arbitrary types and members Foo<T>::type. For example, you could have specializations:
template <> struct Foo<int> { using type = double; };
template <> struct Foo<char> { using type = double; };
template <> struct Foo<float> { using type = bool; };
template <> struct Foo<long> { int type = 10; };
template <> struct Foo<unsigned> { };
If you call g(double{}) there are two possible answers for T, and if you call g(int{}) there is no answer. In general, there is no relationship between class template parameters and class members, so you cannot perform any sensible argument deduction.
Occasionally it is useful to inhibit argument deduction explicitly. This is for example the case for std::forward. Another example is when you have conversions from Foo<U> to Foo<T>, say, or other conversions (think std::string and char const *). Now suppose you have a free function:
template <typename T> bool binary_function(Foo<T> lhs, Foo<T> rhs);
If you call binary_function(t, u), then the deduction may be ambiguous and thus fail. But it is reasonable to deduce only one argument and not deduce the other, thus permitting implicit conversions. Now an explicitly non-deduced context is needed, for example like this:
template <typename T>
struct type_identity {
using type = T;
};
template <typename T>
bool binary_function(Foo<T> lhs, typename type_identity<Foo<T>>::type rhs)
{
return binary_function(lhs, rhs);
}
(You may have experienced such deduction problems with something like std::min(1U, 2L).)

Partial template specialization type collapsing rules

Sorry for the lack of a better title.
While trying to implement my own version of std::move and understanding how easy it was, I'm still confused by how C++ treats partial template specializations. I know how they work, but there's a sort of rule that I found weird and I would like to know the reasoning behind it.
template <typename T>
struct BaseType {
using Type = T;
};
template <typename T>
struct BaseType<T *> {
using Type = T;
};
template <typename T>
struct BaseType<T &> {
using Type = T;
};
using int_ptr = int *;
using int_ref = int &;
// A and B are now both of type int
BaseType<int_ptr>::Type A = 5;
BaseType<int_ref>::Type B = 5;
If there wasn't no partial specializations of RemoveReference, T would always be T: if I gave a int & it would still be a int & throughout the whole template.
However, the partial specialized templates seem to collapse references and pointers: if I gave a int & or a int * and if those types match with the ones from the specialized template, T would just be int.
This feature is extremely awesome and useful, however I'm curious and I would like to know the official reasoning / rules behind this not so obvious quirk.
If your template pattern matches T& to int&, then T& is int&, which implies T is int.
The type T in the specialization only related to the T in the primary template by the fact it was used to pattern match the first argument.
It may confuse you less to replace T with X or U in the specializations. Reusing variable names can be confusing.
template <typename T>
struct RemoveReference {
using Type = T;
};
template <typename X>
struct RemoveReference<X &> {
using Type = X;
};
and X& matches T. If X& is T, and T ia int&, then X is int.
Why does the standard say this?
Suppose we look af a different template specialization:
template<class T>
struct Bob;
template<class E, class A>
struct Bob<std::vector<E,A>>{
// what should E and A be here?
};
Partial specializations act a lot like function templates: so much so, in fact, that overloading function templates is often mistaken for partial specialization of them (which is not allowed). Given
template<class T>
void value_assign(T *t) { *t=T(); }
then obviously T must be the version of the argument type without the (outermost) pointer status, because we need that type to compute the value to assign through the pointer. We of course don't typically write value_assign<int>(&i); to call a function of this type, because the arguments can be deduced.
In this case:
template<class T,class U>
void accept_pair(std::pair<T,U>);
note that the number of template parameters is greater than the number of types "supplied" as input (that is, than the number of parameter types used for deduction): complicated types can provide "more than one type's worth" of information.
All of this looks very different from class templates, where the types must be given explicitly (only sometimes true as of C++17) and they are used verbatim in the template (as you said).
But consider the partial specializations again:
template<class>
struct A; // undefined
template<class T>
struct A<T*> { /* ... */ }; // #1
template<class T,class U>
struct A<std::pair<T,U>> { /* ... */ }; // #2
These are completely isomorphic to the (unrelated) function templates value_assign and accept_pair respectively. We do have to write, for example, A<int*> to use #1; but this is simply analogous to calling value_assign(&i): in particular, the template arguments are still deduced, only this time from the explicitly-specified type int* rather than from the type of the expression &i. (Because even supplying explicit template arguments requires deduction, a partial specialization must support deducing its template arguments.)
#2 again illustrates the idea that the number of types is not conserved in this process: this should help break the false impression that "the template parameter" should continue to refer to "the type supplied". As such, partial specializations do not merely claim a (generally unbounded) set of template arguments: they interpret them.
Yet another similarity: the choice among multiple partial specializations of the same class template is exactly the same as that for discarding less-specific function templates when they are overloaded. (However, since overload resolution does not occur in the partial specialization case, this process must get rid of all but one candidate there.)

How to turn method pointer types into function pointer types

Suppose you have template method that takes a pointer to a method of any type. Inside that template, is there a way to turn the template argument, which is a method pointer type (void(A::*)() for example), into a function pointer type with the same return type and parameters, but without the class information (void(*)())?
I looked at the <type_traits> header but didn't find anything that would help.
A simple way in C++11 is partial specialization with a variadic template:
template<class PMF> struct pmf_to_pf;
template<class R, class C, class... Args>
struct pmf_to_pf<R (C::*)(Args...)>{
using type = R(*)(Args...); // I like using aliases
};
Note that you need a seperate partial specialization for const member-functions (and theoretically for ref-qualified ones aswell):
template<class R, class C, class... Args>
struct pmf_to_pf<R (C::*)(Args...) const>{
using type = R(*)(Args...);
};
With cv-qualifiers ({nil,const}, {nil,volatile}) and ref-qualifiers ({nil, &, &&}), you have a total of 2*2*3 == 12 partial specializations to provide†. You normally can leave out the volatile qualification ones, dropping the total down to "just" 6. Since most compilers don't support function-ref-qualifiers, you only need 2 really, but be aware of the rest.
For C++03 (aka compilers without variadic templates), you can use the following non-variadic form:
template<class PMF> struct pmf_to_pf;
template<class Sig, class C>
struct pmf_to_pf<Sig C::*>{
typedef Sig* type; // 'Sig' is 'R(Args...)' and 'Sig*' is 'R(*)(Args...)'
};
Although this doesn't quite work with cv-qualifiers, since I think C++03 didn't allow cv-qualified signatures (e.g. R(Args...) const) on which you could partially specialize to remove the const.
† The {...} denote a set of a certain qualifier, with nil representing the empty set. Examples:
void f() const& would be {const}, {nil}, {&}
void f() && would be {nil}, {nil}, {&&}
And so on.