Which pattern to generify instantiation of objects that have different Ctor arguments

Viewed 78

I would like to construct a robot with or without a tool, a mobile base, and other parts. As I want to automatize the configuration of the parts, I have a class Robot with the parts as template arguments

For instance, in the code below, the code will build as long as we use tools that have the same constructor signature as ToolInterface. It does build with a Screwdriver but does not with a Gripper.

#include <iostream>
#include <string>

class BaseRobot
{
public:
    BaseRobot(){};
};

class ToolInterface
{
public:
    ToolInterface(BaseRobot* _base, std::string _name):name{_name}{/*register _base*/};
    std::string name;
    bool param_1;
    char param_2;
};

template<class T, class... Args>
constexpr T* construct(Args... args)
{
    if constexpr (std::is_same<T, nullptr_t>::value)
    {
        return nullptr;
    }
    else
    {
        return new T(args...);
    }
};

template<class Tool>
class Robot : public BaseRobot
{
protected:
    Tool* tool;
public:
    Robot():tool(construct<Tool>(this, "tool")){   // <--- here is my problem !!
        if constexpr (! std::is_same<Tool, nullptr_t>::value)
        {
            //do stuff on/with tool->param_1, tool->param_2, ...
            std::cout << "tool configured" << std::endl;
        }
        else
            std::cout << "no tool" << std::endl;
    };
};

class Screwdriver: public ToolInterface
{
public:
    Screwdriver(BaseRobot* _base, std::string _name):ToolInterface(_base, _name){};
};

class Gripper: public ToolInterface
{
public:
    Gripper(BaseRobot* _base, std::string _name, bool _reversed):
        ToolInterface(_base, _name)
        ,reversed{_reversed}{};
    bool reversed;
};

int main()
{
    Robot<Screwdriver> robot_screwdriver;
    Robot<nullptr_t> robot_null;
    //Robot<Gripper> robot_gripper;     //does not build

    return 0;
}

Here are some ideas :

  1. using a ToolConfig struct that is passed as an argument of Tools. If a tool requires more arguments, one should subclass ToolConfig and cast it into the tool constructor (see below): damn, that looks cumbersome and ugly!
  2. enforce inherited ToolInterface classes Ctor signature: some tools must have a different Ctor signature
  3. using a variadic template to pass args into the template: not reasonable because, in the end, I want something like template<class Tool1, class Tool2, class MobileBase, class Camera> class Robot

solution 1 would look like

struct ToolConfig
{
    std::string name;
};
struct GripperConfig : public ToolConfig
{
    bool reversed;
};
class Gripper : public ToolInterface
{
public:
    Gripper(ToolConfig& _config):
        ToolInterface(_config)
        ,reversed{static_cast<GripperConfig&>(_config).reversed}{};
    bool reversed;
};

Do you have a magic pattern to solve my problem? Is my pattern wrong?

2 Answers

You could also use tuple instead of struct, not ideal but this works as well:

#include <iostream>
#include <string>
#include <tuple>


class BaseRobot
{
public:
    BaseRobot() {};
};

class ToolInterface
{
public:
    ToolInterface(std::string _name) :name{ _name } {/*register _base*/ };
    std::string name;
    bool param_1;
    char param_2;
};


template <typename T, typename ... Types, std::size_t ... Indices>
constexpr T* apply_impl(const std::tuple<Types...>& tuple, std::index_sequence<Indices...>)
{
    return new T(std::get<Indices>(tuple)...);
}


template <typename T, typename ... Types>
constexpr T* apply(const std::tuple<Types...>& tuple)
{
    return apply_impl<T>(tuple, std::index_sequence_for<Types...>());
}


template<class T, class... Args>
constexpr T* construct(std::tuple<Args...> args)
{
    if constexpr (std::is_same<T, nullptr_t>::value)
    {
        return nullptr;
    }
    else
    {
        return apply<T>(args);
    }
}


template<class Tool>
class Robot : public BaseRobot
{
protected:
    Tool* tool;
public:
    template<class ...Args1>  //, class ...Args2> 
    Robot(std::tuple<Args1...> p1):  // , std::tuple<Args2...> p2):
    tool(construct<Tool>(p1))
    {   // <--- here is my problem !!
        if constexpr (!std::is_same<Tool, nullptr_t>::value)
        {
            //do stuff on/with tool->param_1, tool->param_2, ...
            std::cout << "tool configured" << std::endl;
        }
        else
            std::cout << "no tool" << std::endl;
    };
};

class Screwdriver : public ToolInterface
{
public:
    Screwdriver(std::string _name) :ToolInterface(_name) {};
};

class Gripper : public ToolInterface
{
public:
    Gripper(std::string _name, bool _reversed) :
        ToolInterface(_name)
        , reversed{ _reversed }{};
    bool reversed;
};

int main()
{
    using p1 = std::tuple<std::string>;
    Robot<Screwdriver> robot_screwdriver(p1{"sdvr"});

    return 0;
}

Could be improved I agree.

You could pass factory lambdas that generate your tools in the initializer.

template<typename Func>
Robot(Func f):tool(f(this, "tool")){   // <--- here is my problem !!
    if constexpr (! std::is_same<Tool, std::nullptr_t>::value)
    {
        //do stuff on/with tool->param_1, tool->param_2, ...
        std::cout << "tool configured" << std::endl;
    }
    else
        std::cout << "no tool" << std::endl;
};

The call site would look like this:

Robot<Screwdriver> robot_screwdriver([](auto... args){ return new Screwdriver(args...); });
Robot<std::nullptr_t> robot_null([](auto...){ return nullptr; });
Robot<Gripper> robot_gripper([](auto... args){ return new Gripper(args..., true); });     

Not exactly beautiful, but it works.

See here for a full example. Does this solve your problem?

If you can use c++17, you can add a class template deduction guide to reduce some of the redundancy at the call site.

Related