Template deduction with smart pointers - c++

My code looks like this:
#include <iostream>
#include <memory>
using namespace std;
class myClass
{
int a;
};
template <typename T, template<typename ValueType> class SmartPtr> // (1)
void myFunction(const void *pointer, SmartPtr<T>& pointer2)
{
cout<<"with template"<<endl;
}
void myFunction(const void *pointer, unique_ptr<myClass>& pointer2)
{
cout<<"without template "<< *(static_cast<const int*>(pointer))<<endl;
}
int main()
{
int* a = new int(5);
unique_ptr<myClass> myUniquePtr(new myClass);
shared_ptr<myClass> mySharedPtr(new myClass);
myFunction(static_cast<const void*>(a), myUniquePtr); // (2)
myFunction<int, unique_ptr<myClass> >(static_cast<const void*>(a), myUniquePtr); // (3)
myFunction<int, shared_ptr<myClass> >(static_cast<const void*>(a), mySharedPtr); // (4)
delete a;
return 0;
}
Calling 'myFunction' in (2) is OK - it calls 'myFunction' without template. But in (3) and (4) compiler can't deduce template arguments. It generates errors:
no matching function for call to ‘myFunction(const void*, std::unique_ptr<myClass>&)'
and
no matching function for call to ‘myFunction(const void*, std::shared_ptr<myClass>&)'
respectively.
The question is how to change the second argument of template in (1) so I can instantiate template with arbitrary smart pointer? I want to omit ValueType and use T.

There is no need to have two template arguments, and this will cause problems for smart pointers that are using a custom allocator. Just have one template argument: the type of smart pointer.
template <typename T>
void myFunction(const void *pointer, T & pointer2)
{
}
So T is the smart pointer type. If you need access to the type of the managed object (myClass in this case) you can use typename std::pointer_traits<T>::element_type.
template <typename T>
void myFunction(const void *pointer, T & pointer2)
{
typedef typename std::pointer_traits<T>::element_type obj_type;
obj_type & o = *pointer2;
}
(Of course if you are using C++11 you could also just use decltype(*pointer2) or even auto.)
With this approach you can even use raw pointers as T, assuming that none of your logic depends on them being smart pointers.
As a side note, your invocation likely fails because you are supplying int as the T template argument, which doesn't make any sense; you should be using myClass there.

We have template <class T, class D = default_delete<T>> class unique_ptr;
So your function template should look like:
template <template<typename, typename> class SmartPtr, typename T, typename D>
void myFunction(const void *pointer, SmartPtr<T, D>& pointer2)
{
std::cout << "with template" << std::endl;
}
but it may be simpler to use something like in cdhowie's answer.

The reason it is complaining is because you are giving it a type as the second template argument, but it expects a template (because you used template-template syntax.) The following should at least get it past the template expansion step of compilation (warning, not compiled, but you should get the idea)
myFunction<int, unique_ptr>(static_cast<const void*>(a), myUniquePtr); // (3)
myFunction<int, shared_ptr>(static_cast<const void*>(a), mySharedPtr); // (4)
But, as others have said, you probably don't need this. Just let template argument deduction happen instead of spelling it out.

Related

Template parameter can't be deduced on implicitly constructed argument

I would like to have the following code in c++17:
#include <iostream>
#include <string>
#include <type_traits>
#include <functional>
class Foo;
template<class T>
class Bar {
public:
std::function<T(Foo&)> m_fn;
template<class Fn>
Bar(Fn fn) : m_fn(fn) {};
T thing(Foo &foo) const {
return m_fn(foo);
}
};
template<class Fn>
Bar(Fn) -> Bar<decltype(std::invoke(std::declval<Fn>(),
std::declval<Foo&>()))>;
class Foo {
public:
Foo() {};
template<class T>
std::vector<T> do_thing(const Bar<T> &b) {
std::vector<T> r;
r.push_back(b.thing(*this));
return r;
}
};
std::string test(Foo &) {
return "hello";
}
int main() {
Foo foo = Foo();
// works
std::vector<std::string> s = foo.do_thing(Bar{test});
// cant deduce T parameter to do_thing
std::vector<std::string> s = foo.do_thing({test});
}
But compiling this gives me "couldn't deduce template parameter ‘T’" on the call to do_thing.
Having do_thing(Bar{test}) fixes this and works fine but equates to some ugly code in the real code equivalent. I would like to have do_thing({test}) or do_thing(test) implicitly construct a Bar and pass that as the argument if possible.
I also don't want to forward declare a variable to pass into do_thing either
Is there some way to guide the inference of template argument T so that the call to do_thing can stay clean?
Edit:
Sorry for the late edit, but the arguments to the Bar constructor are over simplified in the example I included. In reality, there is an extra parameter std::optional<std::string> desc = std::nullopt and that might change in the future (although unlikely). So constructing the Bar inside do_thing would be a bit hard to maintain...
would like to have do_thing({test}) or do_thing(test) implicitly construct a Bar and pass that as the argument if possible.
Unfortunately, when you call do_thing({test}) or do_thing(test), test (or {test}) isn't a Bar<T> object. So the compiler can't deduce the T type and can't construct a Bar<T> object.
A sort of chicken-and-egg problem.
The best I can imagine is to add, in Foo, a do_test() method as follows
template<typename T>
auto do_thing (T const & t)
{ return do_thing(Bar{t}); }
This way you can call (without graphs)
std::vector<std::string> s = foo.do_thing(test);
You get the same result as
std::vector<std::string> s = foo.do_thing(Bar{test});
-- EDIT --
The OP ask
is there any way of preserving the {test} brace syntax? maybe with initializer_list or something?
Yes... with std::initializer_list
template<typename T>
auto do_thing (std::initializer_list<T> const & l)
{ return do_thing(Bar{*(l.begin())}); }
but, this way, you accept also
std::vector<std::string> s = foo.do_thing(Bar{test1, test2, test3});
using only test1
Maybe a little better... another way can be through a C-style array
template <typename T>
auto do_thing (T const (&arr)[1])
{ return do_thing(arr[0]); }
This way you accept only an element.
This happens because {} is not an expression and can only be used in limited ways while doing argument deduction, the parameter must have specific forms in order to succeed.
The allowed parameters types that can be used to deduce template parameters when {} is involved are better expanded in [temp.deduct.call]/1, two of the examples extracted from the cited part of the standard are:
template<class T> void f(std::initializer_list<T>);
f({1,2,3}); // T deduced to int
template<class T, int N> void h(T const(&)[N]);
h({1,2,3}); // T deduced to int
In your example the deduction guide is not used to deduce the T for {test} for the same as above.
foo.do_thing(Bar{test});
is your direct option without using additional functions.

Deduction guides for functions as template parameters

We can use std::unique_ptr to hold a pointer allocated with malloc which will be freed appropriately.
However, the resulting std::unique_ptr's size will be 2 pointers, one for the pointer to the object and one for the pointer to the deleter function instead of the usual 1 pointer to object and an implicit delete. As one answer points out this can be avoided by writing a custom Unique_ptr that knows the proper deleter function. This function can be made known using a template parameter to support any deleter function like so:
template <class T, void (*Deleter)(T *)>
struct Unique_ptr {
explicit Unique_ptr(T *t, void (*)(T *))
: t{t} {}
~Unique_ptr() {
Deleter(t);
}
//TODO: add code to make it behave like std::unique_ptr
private:
T *t{};
};
template <class T>
void free(T *t) {
std::free(t);
}
char *some_C_function() {
return (char *)malloc(42);
}
int main() {
Unique_ptr<char, free> p(some_C_function(), free); //fine
Unique_ptr q(some_C_function(), free); //should be fine
//with the right
//deduction guide
}
This would be really nice if we could use deduction guides to not have to specify the template parameters. Unfortunately I can't seem to get the syntax right. These attempts fail to compile:
template <class T, auto Deleter>
Unique_ptr(T *, Deleter)->Unique_ptr<T, Deleter>;
template <class T, void (*Deleter)(T *)>
Unique_ptr(T *, void (*Deleter)(T *))->Unique_ptr<T, Deleter>;
Alternatively one could write Unique_ptr<free> q(some_C_function()); in order to manually specify the function template parameter, but that creates issues with deducing T.
What is the correct deduction guide to make Unique_ptr q(some_C_function(), free); or Unique_ptr<free> q(some_C_function()); compile?
Why write your own unique_ptr? Just use std::unique_ptr with a custom delete pointer. With C++17, that's very straightforward:
template <auto Deleter>
struct func_deleter {
template <class T>
void operator()(T* ptr) const { Deleter(ptr); }
};
template <class T, auto D>
using unique_ptr_deleter = std::unique_ptr<T, func_deleter<D>>;
Or, as Yakk suggests, more generally:
template <auto V>
using constant = std::integral_constant<std::decay_t<decltype(V)>, V>;
template <class T, auto D>
using unique_ptr_deleter = std::unique_ptr<T, constant<V>>;
which gets you to:
unique_ptr_deleter<X, free> ptr(some_c_api());
Sure, you have to actually write X, but you have no space overhead. In order to accomplish the same thing with deduction guides, you'd need to wrap the function deleter in order to lift it to a template parameter:
template <class T, auto D>
Unique_ptr(T*, func_deleter<D>) -> Unique_ptr<T, func_deleter<D> >;
Which would be used like:
Unique_ptr ptr(some_c_api(), func_deleter<free>());
I'm not sure that's necessarily better, and you run into all the same problems that led to the standard not having deduction guides for std::unique_ptr (i.e.: differentiating between pointers and arrays). YMMV.

c++: why template cannot be used to deduce both container and element type?

I've got a very simple test program like below:
#include<vector>
#include<iostream>
using namespace std;
template<typename C, typename E>
void f(const C<E>& container){
cout<<container.size()<<endl;
}
int main(){
vector<int> i;
f(i);
return 0;
}
It fails to compile with gcc 4.1.2. Error message is:
templateContainer.cpp:5: error: ‘C’ is not a template
templateContainer.cpp: In function ‘int main()’:
templateContainer.cpp:10: error: no matching function for call to ‘f(std::vector<int, std::allocator<int> >&)’
You could use a template template parameter (and note that std::vector actually takes more than one template parameter [an element type, and an allocator type]).:
template<template <typename...> class C, typename... E>
void f(const C<E...>& container){
cout<<container.size()<<endl;
}
Live Demo
If you don't need the type decompositions, you could simply use an ordinary template.
template<typename C>
void f(const C& container){
cout<<container.size()<<endl;
}
You can additionally obtain typedefs from STL containers: for example, if you want to know the type of elements held by the container, value_type is there for you.
template<typename C>
void f(const C& container){
using ValueType = typename C::value_type;
cout<<container.size()<<endl;
}
std::vector has two template arguments, type and allocator.
template <template<class, class> class C, class E, class A>
void f(const C<E, A> &container)
{
std::cout << container.size() << endl;
}
int main()
{
std::vector<int> i;
f(i);
return 0;
}
Although WhiZTiM's answer is correct (well, preferring the second part), it doesn't explain why your code doesn't work.
Assuming for the moment that you intended roughly
template<template <typename> class C, typename E> void f(const C<E>&);
the reason that std::vector doesn't match is that it is the wrong shape - it has two type parameters, not one as in your declaration.
Just because you don't often explicitly write the defaulted second (allocator) param, doesn't mean it isn't there.
For comparison, this works (or doesn't) in an analogous way:
void f(int);
void g(int, int* = nullptr);
void apply(void (*func)(int), int);
apply(f, 42); // ok - f matches shape void(*)(int)
apply(g, 42); // error - g is really void(*)(int,int*)
specifically, default arguments (or type parameters) are syntactic sugar. They allow you to forget about those arguments at the call (instantiation) site, but don't change the shape of the function (or template).

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) { ... }

Determining the type from a smart pointer

I have a function that currently takes in two template parameters. One is expected to be the smart pointer, and the other is expected to be the object type. For example, SmartPtr<MyObject> as the first template parameter and MyObject as the second template parameter.
template <typename T, typename TObject>
I would like to know whether I can determine the second parameter, MyObject, automatically from the first parameter SmartPtr<MyObject> or not so that my template function is written like this:
template <typename T>
And the type TObject in the original template function is automatically determined from T which is expected to be a smart pointer.
As requested, here is the function declaration and its use:
template <typename T, typename TObject>
T* CreateOrModifyDoc(T* doc, MyHashTable& table)
{
T* ptr = NULL;
if (!table.FindElement(doc->id, ptr))
{
table.AddElement(doc->id, new TObject());
table.FindElement(doc->id, ptr);
}
return ptr;
}
If you know that the first template parameter will be the smart pointer type, why not declare your function with only one parameter and use it as such:
template<typename T>
void WhatIsIt(SmartPtr<T> ptr)
{
printf("It is a: %s" typeid(T).name());
}
If the classes that can serve as the first template parameter can be made to provide a handy typedef by a common name, you can do this:
template <typename T>
class SmartPtr
{
public:
typedef T element_type;
// ...
};
template <typename PtrType, typename ObjType=PtrType::element_type>
void your_function_here(const PtrType& ptr)
{
// ...
}
Did you write SmartPtr? If so, add this to it
typedef T element_type;
All smart pointers I know of support the member ::element_type. For example boost's shared_ptr: http://www.boost.org/doc/libs/1_46_1/libs/smart_ptr/shared_ptr.htm#element_type, but also the std smart pointers support this convention.
template <typename T> class SharedPtr {
public:
typedef T element_type;
// ...
};