1

Consider this piece of code creating, based on a condition, a different class instance through a std::make_shared. Note that the two possible classes used (Child1 and Child2) are having the same construction parameters.

class Base
{
public:
    Base( int param )
    {
    }
};

class Child1 : public Base
{
public:
    using Base::Base;
};

class Child2 : public Base
{
public:
    using Base::Base;
};

std::shared_ptr<Base> getPtr( bool cond, int param )
{
    if ( cond )
        return std::make_shared<Child1>(param);
    else
        return std::make_shared<Child2>(param);
}

int main(int argc, char* argv[]) {
    bool permissive = ...;
    Test::getPtr( permissive, argc );
    return 0;
}

Note that permissive is unknown at compilation time.

Is there a way to factorize argument list management in getPtr function with something like:

return std::make_shared<cond?Child1:Child2>(param);

This obviously does not compile but I could not find a way to make something similar work even using templates...

jpo38
  • 20,821
  • 10
  • 70
  • 151
  • No, you can't. You have to use `if` or `switch` and specify a different class in each branch. If you have many of them, you can write a factory that maps `cond` to a function that creates the object, but this just moves the problem around. – j6t Dec 09 '21 at 11:02
  • If `cond `is known only at runtime there is nothing to be done. If it is known at compile time it can be made with `std::conditional` for example – bartop Dec 09 '21 at 11:04
  • @bartop, it's known at runtime – jpo38 Dec 09 '21 at 11:05
  • @j6t: Finally found a way to share the code handling parameters, see my answer. – jpo38 Dec 09 '21 at 11:11
  • `(cond ? +[](int i) -> std::shared_ptr { return std::make_shared(i);} : +[](int i) -> std::shared_ptr { return std::make_shared(i);})(3);` would indeed avoid to repeat argument, but it seems worst than your simple `if` – Jarod42 Dec 09 '21 at 11:11
  • @Jarod42: It's not that bad. In my MCVE there's only one parameter, but I'm trying to find a solution because in my real code there are many of them! – jpo38 Dec 09 '21 at 11:29

3 Answers3

2

Best solution I could find to avoid duplicating code managing parameters is

class Helper
{
public:
    int param;

    template<class T> 
    std::shared_ptr<Base> create( int param )
    {
        return std::make_shared<T>(param);
    }
};

std::shared_ptr<Base> getPtr( bool cond, int param )
{
    Helper helper{param};
    return cond ? helper.create<Child1>() : helper.create<Child2>();
}
jpo38
  • 20,821
  • 10
  • 70
  • 151
  • this solution bypasses the requriement of passing parameters to the constructor. What I mean is, if thats a viable solution you could as well provide default contructors and call them instead of the helper – 463035818_is_not_an_ai Dec 09 '21 at 11:14
  • It does need to pass a parameter. It's always 3 in my MCVE example, but it cannot be a default parameter, I'll update the posts to show this. – jpo38 Dec 09 '21 at 11:26
2

With tuple, you might have only one parameter, so, something like:

std::shared_ptr<Base> getPtr(bool cond, /*lot_of_params*/)
{
    std::tuple t{lot_of_params};

    if (cond)
        return std::apply([](auto&&... args){return std::make_shared<Child1>(args...);}, t);
    else
        return std::apply([](auto&&... args){return std::make_shared<Child2>(args...);}, t);
}
Jarod42
  • 203,559
  • 14
  • 181
  • 302
0

Here is generalized solution i came up with while figuring similar problem. Works with several classes and several forwarded arguments. Uses indexing instead of branching. Does not suffer from code duplication.

#include <array>
#include <memory>
#include <utility>
#include <cstddef>

template<typename T, typename... Args> 
::std::shared_ptr<Base> Impl(Args &&... args)
{
    return ::std::make_shared<T>(::std::forward<Args>(args)...);
}

template<typename... T, typename... Args>
::std::shared_ptr<Base> Make(::std::size_t const index, Args &&... args)
{
    return ::std::array{Impl<T, Args...>...}[index](::std::forward<Args>(args)...);
}
...
int main()
{
    auto cond{true};
    auto p_base{Make<Child1, Child2>(cond, 42)};
}

online compiler

user7860670
  • 35,849
  • 4
  • 58
  • 84