Template deduction with templated class and const-qualification in smart pointers - c++

Let's say I've got a templated class, and a function that accepts it in a shared pointer to const, using its template parameter as part of its signature:
template <class T>
class SomeClass {};
// Doesn't need to modify the SomeClass object.
template <class T>
T DoSomething(std::shared_ptr<const SomeClass<T>>);
In this case I can call DoSomething using a shared pointer to non-cost SomeClass by explicitly specifying the template parameter:
DoSomething<int>(std::make_shared<SomeClass<int>>());
But this doesn't work without being explicit, because type deduction fails.
How can I make the function callable with type deduction in this case? Obviously I could write another overload that accepted a shared pointer to non-const, but it's a drag to need to define every function of this type twice.
Ideally this would fail for inputs that aren't correct (shared pointers to other things, or non-shared pointers) at overload resolution time.

One possibility is to use a helper to check for the right type while letting DoSomething accept any T (and fail to compile for the wrong T):
#include <iostream>
#include <type_traits>
#include <iomanip>
#include <memory>
#include <type_traits>
template <typename T>
struct Foo{};
template <typename T>
struct is_right_type : std::false_type {};
template <typename X>
struct is_right_type<std::shared_ptr<const Foo<X>>> : std::true_type {};
template <class T>
auto DoSomething(T t){
static_assert(is_right_type<T>::value);
return 42;
}
int main()
{
//DoSomething(42); // ERROR
std::shared_ptr<const Foo<int>> x;
std::shared_ptr<Foo<int>> y;
DoSomething(x); // OK
DoSomething(y); // ERROR
}
You'll might need to do something extra to get the return type correctly deduced.

Related

Passing unique_ptr<Derived<T>> to a function

I need to pass a unique pointer to a derived template class to a function that takes a unique base template class, like this:
template <typename T>
class Base {};
template <typename T>
class Derived : public Base<T> {};
template <typename T>
void foo(std::unique_ptr<Base<T>>){}
//or
template <typename T>
class MyClass{
public:
MyClass(std::unique_ptr<Base<T>> arg) : _arg(std::move(arg)) {}
private:
std::unique_ptr<Base<T>> _arg;
};
int main()
{
auto b = make_unique<Derived<int>>();
foo(std::move(b));
MyClass mc(std::move(b))
}
Why is this not working and how can I fix it?
I get an error:
'void foo1<T>(std::unique_ptr<Base<T>,std::default_delete<Base<T>>>)': cannot convert argument 1 from 'std::unique_ptr<Derived<int>,std::default_delete<Derived<int>>>' to 'std::unique_ptr<Base<T>,std::default_delete<Base<T>>>'
but it work
auto derived = std::make_unique<Derived<int>>();
std::unique_ptr<Base<int>> base = std::move(derived);
C++ doesn't deduce template arguments in this situation. You can specify <int>, and that will succeed.
foo<int>(std::move(b)); // fine
MyClass<int> mc(std::move(b)); // fine
See it on coliru
You can't have template argument deduction also consider implicit conversions, at least not in most situations. Normally the argument type must match the parameter type exactly for deduction of a template argument to be possible (in this case to deduce T), but std::unique_ptr<Base<int>> and std::unique_ptr<Dervived<int>> are not the same type.
As the other answer suggests you can explicitly specify the template argument instead of trying to have it be deduced.
If you want to automate this without having to add anything to Derived or Base you can however make use of one of the exceptions to the general rule above. If the template parameter is a reference-to or pointer-to base of the argument type, then it may (with certain conditions) still be used for deduction:
// Here an exception to the deduction rules applies
// and `Base<T>*` can be deduced against a pointer `X*`
// if `X` is (uniquely) derived from a `Base<T>`
template<typename T>
auto as_base_ptr(Base<T>* p){
return p;
}
template<typename X>
auto to_base_unique_ptr(std::unique_ptr<X> p) {
using base_type = std::remove_pointer_t<decltype(as_base_ptr(std::declval<X*>()))>;
return std::unique_ptr<base_type>(std::move(p));
}
template <typename T>
void foo(std::unique_ptr<Base<T>>){
}
template <typename X>
void foo(std::unique_ptr<X> p){
foo(to_base_unqiue_ptr(std::move(p)));
}
But even simpler you can ask yourself whether you really need to have the function foo take std::unique_ptr<Base<T>> specifically (e.g. because you need access to T) or whether std::unique_ptr<X> wouldn't already be enough.

Passing a std::shared_ptr<T> to a function that takes a std::shared_ptr<const T>?

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:
Modifying the calls to func. This is part of a larger code refactoring, and I would prefer not to have to use std::const_pointer_cast at every call site.
Defining multiple overloads of func as that seems redundant.
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.
As the const is only for documentation, make it a comment:
template <class T>
void func(std::shared_ptr</*const*/ T> p) {
}
You could additionally delegate to the version getting a pointer to constant object if the function is hefty enough to make it worthwhile:
template <class T>
void func(std::shared_ptr</*const*/ T> p) {
if (!std::is_const<T>::value) // TODO: constexpr
return func<const T>(std::move(p));
}
No guarantee the compiler will eliminate the move though.
You certainly don't want to be modifying the call sites, but you sure can be modifying the functions themselves - that's what you implied in the question anyway. Something had to be changed somewhere, after all.
Thus:
In C++17 you could use deduction guides and modify call sites, but in a less intrusive manner than with a cast. I'm sure a language lawyer can pitch in about whether adding a deduction guide to the std namespace is allowed. At the very least we can limit the applicability of those deduction guides to the types we care about - it won't work for the others. That's to limit potential for mayhem.
template <typename T>
class allow_const_shared_ptr_cast : public std::integral_constant<bool, false> {};
template <typename T>
static constexpr bool allow_const_shared_ptr_cast_v = allow_const_shared_ptr_cast<T>::value;
template<>
class allow_const_shared_ptr_cast<int> : public std::integral_constant<bool, true> {};
template <typename T>
void func(std::shared_ptr<const T> ptr) {}
namespace std {
template<class Y> shared_ptr(const shared_ptr<Y>&) noexcept
-> shared_ptr<std::enable_if_t<allow_const_shared_ptr_cast_v<Y>, const Y>>;
template<class Y> shared_ptr(shared_ptr<Y>&&) noexcept
-> shared_ptr<std::enable_if_t<allow_const_shared_ptr_cast_v<Y>, const Y>>;
}
void test() {
std::shared_ptr<int> nonConstInt;
func(std::shared_ptr(nonConstInt));
func(std::shared_ptr(std::make_shared<int>()));
}
std::shared_ptr is certainly less wordy than std::const_pointer_cast<SomeType>.
This should not have any performance impact, but sure modifying the call sites is a pain.
Otherwise there's no solution that doesn't involve modifying the called function declarations - but the modification should be acceptable, I think, since it's not any more wordy than what you had already:

Are there any means to allow for more complex type inference in C++ templates?

Suppose I have a template function:
template<class T>
void whenMatchesType(std::function<void(T*)> action) { ... }
I might invoke this like so:
anObject.whenMatchesType<SomeType>([=](SomeType *value) {
// ...
});
Although C++ is capable of inferring template parameters from arguments of simple, non-template types, I don't seem to be able to omit explicitly specifying the type (as <SomeType>) in this case - even though it is provided as a type parameter to the first argument.
Is there some change to my code - or to my compilation - through which I might avoid this redundancy?
If you need acces to the parameter type you can still take in the callable as it's own template parameter, then use type traits to extract the information.
Here is a simple example.
#include <iostream>
#include <functional>
template <typename T>
struct parameter_type;
template <typename ReturnType, typename ParameterType>
struct parameter_type<std::function<ReturnType(ParameterType)>> {
using ParameterT = ParameterType;
};
template <typename T>
using param_t = typename parameter_type<decltype(std::function{std::declval<T>()})>::ParameterT;
template<class T>
void whenMatchesType(T) {
using Parameter = param_t<T>;
static_assert(std::is_same_v<Parameter, int>, "Only callables that take int as parameter is allowed");
}
int main() {
whenMatchesType([](int){});
//whenMatchesType([](double){});
}

How can I use a nested type belonging to a templated class in another template function in C++?

I'm setting up a function that initializes tuples based on a tuple type and a functor struct For that has a size_t template argument INDEX to retain the compile-time index. This functor may also depend on other template arguments T.... Because of this the functors exist within other structures (TClass in this example) that hold these template arguments.
The initialization function (called Bar here) has a template<std::size_t> class template argument to ensure that the used class actually can store the index.
While the design I've come up with works fine when I call it from a non-template function, it does not compile if the template T2 of a function does determine the template parameter of the wrapper TClass.
Here is the definition of the functor For wrapped inside TClass:
#include <cstdlib>
template <typename T> struct TClass {
template<std::size_t INDEX> struct For {
void operator()() {}
};
};
And here are the function calls i want to use:
template <template<std::size_t> class FOR> void bar() {
//...
}
template <typename T> void foo() {
bar<TClass<T>::For>(); //Does not compile
}
int main() {
bar<TClass<int>::For>(); //Works
foo<int>();
return 0;
}
The compiler output for the faulty foo-call is:
error: dependent-name ‘TClass<T>::For’ is parsed as a non-type, but instantiation yields a type
Bar<TClass<T>::For>(); //Does not compile
I know that dependent type names usually have to be preceded by a typename but this is also not necessary for the first bar-call. I assumed it was because the template argument can only be interpreted as a type. So I thought that maybe typename would result in correct compilation but if I change foo to
template <typename T> void foo() {
bar<typename TClass<T>::For>(); //Does not compile
}
I get:
error: ‘typename TClass<int>::For’ names ‘template<long unsigned int INDEX> struct TClass<int>::For’, which is not a type
Bar<typename TClass<T>::For>(); //Does not compile
I've also come up with a design where the ()-operator of TClass depends on the template INDEX which also works fine because it is not necessary to use nested types anymore. It looks like this:
#include <cstdlib>
template <typename T> struct TClass {
template<std::size_t INDEX> void operator()() {}
};
template <typename FOR> void bar() {
//...
}
template <typename T> void foo() {
bar<TClass<T>>(); //Does compile
}
Apparently it is not possible to use dependent type names in functions where the template of the type is determined by the function's template parameters, but why? And how do I implement this correctly? To make writing future type checks with type traits easier I would prefer it if I can use a functor.
The compiler cannot know that TClass<T>::For refers to a template at the first stage of template instantiation. It needs a bit of help with template keyword. Fix:
template <typename T> void foo() {
bar<TClass<T>::template For>();
}

overloading of template template function

I am trying to declare a function that checks wether a smart pointer is initialized. I wrote two variants of a function that acts on smart pointers, one template function acting on templates, one acting on a template of a template. The problem is, the latter one is supposed to act on at least std::unique_ptr and std::shared_ptr. The std::unique_ptr one is constructed differently from std::shared_ptr. std::unique_ptr one takes two template arguments (namely object type and deleter) whereas std::shared_ptr one takes only one (namely the object type).
#include <iostream>
#include <memory>
template <typename Smartpointer>
void checkPointerinitialization(const Smartpointer& ptr){
if(!ptr.get())
std::cout<<"smart pointer is not initialized\n"<<std::endl;
}
//template <template <typename, typename> class Smartpointer, typename Object, typename Deleter>
//void checkPointerinitializationWithTemplates(const Smartpointer<Object, Deleter>& ptr){
// if(!ptr.get())
// std::cout<<"smart pointer is not initialized in template function either\n"<<std::endl;
//}
//int main(){
// std::shared_ptr<int> myptr;
// checkPointerinitialization(myptr);
// checkPointerinitializationWithTemplates(myptr);
//
//}
template <template <typename> class Smartpointer, typename Object>
void checkPointerinitializationWithTemplates(const Smartpointer<Object>& ptr){
if(!ptr.get())
std::cout<<"smart pointer is not initialized in template function either\n"<<std::endl;
}
int main(){
std::shared_ptr<int> myptr;
checkPointerinitialization(myptr);
checkPointerinitializationWithTemplates(myptr);
}
My solution was to overload the template function, however if I uncomment the first function, I get a template deduction failed - wrong number of template arguments error message from g++. Is it actually possible to overload the template functions in an appropriate manner?
Instead of taking your argument as an instantiation of a class template with one single template parameter, take your argument as a class template taking at least one template parameter:
template <template <class, class...> class Z,
class Object,
class... Ts>
void foo(const Z<Object, Ts...>& ptr) { ... }
This will match both std::unique_ptr<X> (with Object=X and Ts={std::default_delete<X>}) and std::shared_ptr<X> (with Object=X and Ts={}).
However, this will not match any non-template smart pointers, like:
struct MySpecialSnowflakeSmartPointer { ... };
So your first approach is probably best:
template <class SP>
void foo(SP const& ptr) { ... }