How to avoid having to use "std::static_pointer_cast" when calling a friend function of a template base class with a derived instance

Viewed 233

I have a template base class with a friend function that can make an operation (connect) with another class. If I derive a class from such template base I need to make use of std::static_pointer_cast<base<whatever_type>> when calling the friend function passing a derived class instance I would like to find a way for the friend function to accept an instance of the derived class without having to perform the static_pointer_cast. I guess I need to find a way to declare the friend function such that template argument deduction would work it out?

Code below:

#include <memory>
#include <queue>
#include <vector>
#include <unordered_map>
    
template<class T>
class base;
    
template<class T>
class extractor;
    
template<class T>
auto connect(std::shared_ptr<base<T>> node,
             std::shared_ptr<extractor<T>> ext);    
    
// A queue adaptator that allows only to pop elements
template<class ValueType>
class extractor
{
public:
    friend auto connect<ValueType>(std::shared_ptr<base<ValueType>>,
                                   std::shared_ptr<extractor<ValueType>>);

    void pop()
    {
        queue->pop();
    }
      
private:
    std::shared_ptr<std::queue<ValueType>> queue;
};
    
    
// A collection of replicated queues that allows only to push elements
template<class T = void*>
class base
{
public:
    
    friend auto connect<T>(std::shared_ptr<base<T>>,
                           std::shared_ptr<extractor<T>>);
    
    void push(const T& e)
    {
        for (auto & o : output)
        {
            o.second->push(e);
        }
    }
    
private:
  std::unordered_map<std::shared_ptr<extractor<T>>,
                     std::shared_ptr<std::queue<T>>> output;
};
    
template<class T>
auto connect(std::shared_ptr<base<T>> node,
             std::shared_ptr<extractor<T>> ext)
{
    if (ext == nullptr)
    {
        ext.reset(new extractor<T>);
    }
        
    auto q = std::make_shared<std::queue<T>>();
    node->output.emplace(ext, q);
    ext->queue = q;   
}
    
    
// A user type that puts number 42
class foo : public base<int>
{
public:
    
  void do_something()
  {
      push(42);
  }
    
};
    
// A user type that pops from an input
class bar : public base<>
{
public:
    std::shared_ptr<extractor<int>> input;
      
    void do_something_else()
    {
        input->pop();
    }
};   
    
int main(int argc, char** argv)
{
    auto pusher = std::make_shared<foo>();
    auto poper  = std::make_shared<bar>();
    
    // Here is the problem: I do not want client code to have to use "static_pointer_cast"
    connect(std::static_pointer_cast<base<int>>(pusher), poper->input);
    
    // Would like to be able to use:
    connect(pusher, poper->input);
        
    return 0;
}

Clang error if I do not do the static_pointer_cast:

stack_overflow.cpp:107:5: error: no matching function for call to 'connect'
    connect(pusher, poper->input);
    ^~~~~~~
stack_overflow.cpp:58:6: note: candidate template ignored: could not match 'base<type-parameter-0-0>' against 'foo'
auto connect(std::shared_ptr<base<T>> node,
1 Answers

The problem is in the fact that the compiler will try to deduce the type T from the given argument and it can't resolve foo to the argument base<int>. Casting to a base type is not considered when trying to match a template argument, in contrast to matching a function from an overload set.

In this particular case, because the type T can already be determined from the second function argument, you can tell the compiler not to attempt to match the first argument, for instance by changing the declaration of connect() in this way:

template <typename T>
struct identity
{
    using type = T;
};

template<class T>
auto connect(std::shared_ptr<base<typename identity<T>::type>> node,
             std::shared_ptr<extractor<T>> ext);  

Plugging this into your complete example renders the following:

#include <memory>
#include <queue>
#include <vector>
#include <unordered_map>
    
template<class T>
class base;
    
template<class T>
class extractor;

template <typename T>
struct identity
{
    using type = T;
};

template<class T>
auto connect(std::shared_ptr<base<typename identity<T>::type>> node,
             std::shared_ptr<extractor<T>> ext);    
    
// A queue adaptor that allows only to pop elements
template<class ValueType>
class extractor
{
public:
    friend auto connect<ValueType>(std::shared_ptr<base<typename identity<ValueType>::type>> ,
                                   std::shared_ptr<extractor<ValueType>>);
    
    void pop()
    {
        queue->pop();
    }
      
private:
    std::shared_ptr<std::queue<ValueType>> queue;
};
    
    
// A collection of replicated queues that allows only to push elements
template<class T = void*>
class base
{
public:
    
    friend auto connect<T>(std::shared_ptr<base<typename identity<T>::type>>,
                           std::shared_ptr<extractor<T>>);
    
    void push(const T& e)
    {
        for (auto & o : output)
        {
            o.second->push(e);
        }
    }
    
private:
  std::unordered_map<std::shared_ptr<extractor<T>>,
                     std::shared_ptr<std::queue<T>>> output;
};


template<class T>
auto connect(std::shared_ptr<base<typename identity<T>::type>> node,
             std::shared_ptr<extractor<T>> ext)
{
    if (ext == nullptr)
    {
        ext.reset(new extractor<T>);
    }
        
    auto q = std::make_shared<std::queue<T>>();
    node->output.emplace(ext, q);
    ext->queue = q;   
}
    
    
// A user type that puts number 42
class foo : public base<int>
{
public:
    
  void do_something()
  {
      push(42);
  }
    
};
    
// A user type that pops from an input
class bar : public base<>
{
public:
    std::shared_ptr<extractor<int>> input;
      
    void do_something_else()
    {
        input->pop();
    }
};   
    
int main(int argc, char** argv)
{
    auto pusher = std::make_shared<foo>();
    auto poper  = std::make_shared<bar>();
    
    // Here is the problem: I do not want client code to have to use "static_pointer_cast"
    connect(std::static_pointer_cast<base<int>>(pusher), poper->input);
    
    // Would like to be able to use:
    connect(pusher, poper->input);
        
    return 0;
}
Related