Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Template function to discern smart_pointer from other container

Consider the following template functions:

template <class T>
const T* DoSomething(const T& t)
{
    auto& id = typeid(T);
    cout << "Type is " << id.name() << ", and we have a ";
    cout << "ref of one\n";
    return &t;
}

template <class T>
T* DoSomething(T* t)
{
    auto& id = typeid(T);
    cout << "Type is " << id.name() << ", and we have a ";
    cout << "pointer to one \n";
    return t;
}

template <class T, template <class> class container>
T* DoSomething(const container<T>& t)
{
    auto& type_id = typeid(T);
    auto& container_id = typeid(container<T>);
    cout << "Type is " << type_id.name() << ", and we have a ";
    cout << container_id.name() << "of one\n";
    return t.get();
}

template <class T, template <class,class> class container, template <class> class deleter = default_delete>
T* DoSomething(const container<T, deleter<T>>& t)
{
    auto& type_id = typeid(T);
    auto& container_id = typeid(container<T,deleter<T>>);
    cout << "Type is " << type_id.name() << ", and we have a ";
    cout << container_id.name() << "of one\n";
    return t.get();
}

The goal is to be able to pass to them either a plain reference, pointer, or smart pointer, and use overloading and template specilization in order for the correct function to be called. The following driving code works as expected:

char r('r');
DoSomething(r);  
DoSomething(&r);
shared_ptr<char> s(new char ('s'));
unique_ptr<char> u(new char ('u'));
DoSomething(s);
DoSomething(u);

However, consider what happens if we try this:

vector<int> v {1,2};
DoSomething(v);

Now, we we get a compile error. The version of DoSomething that the compiler decides to use is the 4th one. Inside this, we reference a function get(), which vector does not have. If the compiler were to somehow pick the 1st defintion of DoSomething, it would compile fine, and work as I intend.

So can I restrict the 3rd and 4th specializations to only be matched when the template template parameter contains a get() method? Is there a way I can get this to happen, perhaps using traits, SFINAE, or some other more advanced template technique?

like image 498
Tom Davies Avatar asked Mar 22 '23 15:03

Tom Davies


1 Answers

The version of DoSomething that the compiler decides to use is the 4th one.

Because std::vector<T, std::allocator<T>> is an exact match for the template parameter container and std::allocator<T> is an exact match for the template parameter deleter, and const container<T, deleter<T>>& is more specialized than const T& so the 4th overload is chosen as the best match by the partial ordering rules for function templates.

So can I restrict the 3rd and 4th specializations to only be matched when the template template parameter contains a get() method?

Yes, you can tell the compiler that the function returns whatever t.get() returns:

template <class T, template <class> class container>
auto DoSomething(const container<T>& t) -> decltype(t.get())
{
    auto& type_id = typeid(T);
    auto& container_id = typeid(container<T>);
    cout << "Type is " << type_id.name() << ", and we have a ";
    cout << container_id.name() << "of one\n";
    return t.get();
}

If t.get() is not a valid expression then template argument deduction fails, because the argument T cannot be substituted into the function signature successfully, and so the function will not be a viable overload, and the 1st overload will be used instead.

like image 194
Jonathan Wakely Avatar answered Apr 26 '23 13:04

Jonathan Wakely