Search code examples
c++templatesboost-optional

couldn't deduce template parameter


I am trying to use an API which is similar to the following:

#include<iostream>
#include<boost/optional.hpp>

class Base
{
 int id;
public:
 int get_id()
 {
    return id;
 }
};

class A : public Base
{
};

class B : public Base
{
};

class M
{
public:
    enum Type
    {
     t_A,
     t_B
    };
    Type type;
    boost::optional<A&> a;
    boost::optional<B&> b;
    boost::optional<A&> get_A()
    {
        return a;
    }

    boost::optional<B&> get_B()
    {
        return b;
    }

};

I need to get to the base through any derived class. So I created a templated function like this:

template<class T>
boost::optional<T&> get(M & m)
{
    switch(m.type)
    {
    case M::t_A :
        return m.get_A();
    case M::t_B :
        return m.get_B();
    default:
        throw;
    };
}

int main()
{
    M m;
    //... initialization of m
    int i = get<>(m)->get_id();
    return 0;
}

but my function's template argument cannot be deduced :

template_sp_1.cpp:63:17: error: no matching function for call to ‘get(M&)’
  int i = get<>(m)->get_id();
                 ^
template_sp_1.cpp:63:17: note: candidate is:
template_sp_1.cpp:46:21: note: template<class T> boost::optional<T&> get(M&)
 boost::optional<T&> get(M & m)
                     ^
template_sp_1.cpp:46:21: note:   template argument deduction/substitution failed:
template_sp_1.cpp:63:17: note:   couldn't deduce template parameter ‘T’
  int i = get<>(m)->get_id();

trying any of the following is not possible; apparently due to using boost::optional :

int i = get<Base>(m)->get_id();
int i = get<A>(m)->get_id();
int i = get<B>(m)->get_id();

Do you have a solution for such scenarios? (I can't touch the API)


Solution

  • As @Manu343726 already pointed out, your get() function has varying return types. But since A and B have the common base class Base, why don't you use a return type of Base&?

    There is a bug in boost::optional regarding references which was fixed in boost 1.58, so you need at least this version. I modified your example to show how it could work:

    #include <boost/optional.hpp>
    #include <boost/version.hpp> 
    #include <iostream>
    
    
    #if BOOST_VERSION < 105800
    #error boost version must be at least 1.58
    #endif
    
    class Base
    {
     int id;
    public:
     Base(int id) : id(id) {}
     int get_id()
     {
        return id;
     }
    };
    
    class A : public Base
    {
    public:
        A() : Base(100) {}
    };
    
    class B : public Base
    {
    public:
        B() : Base(999) {}
    };
    
    class M
    {
    public:
        enum Type
        {
         t_A,
         t_B
        };
        Type type;
        boost::optional<A&> a;
        boost::optional<B&> b;
        boost::optional<A&> get_A()
        {
            return a;
        }
    
        boost::optional<B&> get_B()
        {
            return b;
        }
    
    };
    
    
    Base& get(M & m)
    {
        switch(m.type)
        {
        case M::t_A :
            return (*(m.get_A()));
        case M::t_B :
            return (*(m.get_B()));
        default:
            throw;
        };
    }
    
    int main()
    {
        A a;
        M m;
        m.type = M::t_A;
        m.a = a;
        Base& base = get(m);
        std::cout << base.get_id() << std::endl;
        return 0;
    }
    

    This example will output:

    100
    

    This of course only works at runtime if the API does guarantee that get_A() or get_B() return an optional containing a valid reference. If this is not guaranteed, you can use something like this:

    boost::optional<Base&> get(M & m)
    {
        boost::optional<Base&> base;
        switch(m.type)
        {
        case M::t_A:
            base = m.get_A();
            break;
        case M::t_B :
            base = m.get_B();
            break;
        default:
            throw;
        };
        return base;
    }