I have a function that needs to take shared ownership of an argument, but does not modify it. I have made the argument a shared_ptr<const T> to clearly convey this intent.
template <typename T>
void func(std::shared_ptr<const T> ptr){}
I would like to call this function with a shared_ptr to a non-const T. For example:
auto nonConstInt = std::make_shared<int>();
func(nonConstInt);
However this generates a compile error on VC 2017:
error C2672: 'func': no matching overloaded function found
error C2784: 'void func(std::shared_ptr<const _Ty>)': could not deduce template argument for 'std::shared_ptr<const _Ty>' from 'std::shared_ptr<int>'
note: see declaration of 'func'
Is there a way to make this work without:
We are currently compiling against the C++14 standard, with plans to move to c++17 soon, if that helps.
template <typename T>
void cfunc(std::shared_ptr<const T> ptr){
// implementation
}
template <typename T>
void func(std::shared_ptr<T> ptr){ return cfunc<T>(std::move(ptr)); }
template <typename T>
void func(std::shared_ptr<const T> ptr){ return cfunc<T>(std::move(ptr)); }
this matches how cbegin
works, and the "overloads" are trivial forwarders with nearly zero cost.
Unfortunately, there is no good solution to what you desire. The error occurs because it fails to deduce template argument T
. During argument deduction it attempts only a few simple conversations and you cannot influence it in any way.
Think of it: to cast from std::shared_ptr<T>
to some std::shared_ptr<const U>
it requires to know U
, so how should compiler be able to tell that U=T
and not some other type? You can always cast to std::shared_ptr<const void>
, so why not U=void
? So such searches aren't performed at all as in general it is not solvable. Perhaps, hypothetically one could propose a feature where certain user-explicitly-declared casts are attempted for argument deduction but it isn't a part of C++.
Only advise is to write function declaration without const
:
template <typename T>
void func(std::shared_ptr<T> ptr){}
You could try to show your intent by making the function into a redirection like:
template <typename T>
void func(std::shared_ptr<T> ptr)
{
func_impl<T>(std::move(ptr));
}
Where func_impl
is the implementation function that accepts a std::shared_ptr<const T>
. Or even perform const cast directly upon calling func_impl
.
Thanks for the replies.
I ended up solving this a slightly different way. I changed the function parameter to just a shared_ptr
to any T
so that it would allow const types, then I used std::enable_if
to restrict the template to types that I care about. (In my case vector<T>
and const vector<T>
)
The call sites don't need to be modified. The function will compile when called with both shared_ptr<const T>
and shared_ptr<T>
without needing separate overloads.
Here's a complete example that compiles on VC, GCC, and clang:
#include <iostream>
#include <memory>
#include <vector>
template<typename T>
struct is_vector : public std::false_type{};
template<typename T>
struct is_vector<std::vector<T>> : public std::true_type{};
template<typename T>
struct is_vector<const std::vector<T>> : public std::true_type{};
template <typename ArrayType,
typename std::enable_if_t<is_vector<ArrayType>::value>* = nullptr>
void func( std::shared_ptr<ArrayType> ptr) {
}
int main()
{
std::shared_ptr< const std::vector<int> > constPtr;
std::shared_ptr< std::vector<int> > nonConstPtr;
func(constPtr);
func(nonConstPtr);
}
The only downside is that the non-const instantiation of func
will allow non-const methods to be called on the passed-in ptr. In my case a compile error will still be generated since there are some calls to the const version of func
and both versions come from the same template.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With