2
\$\begingroup\$

I combined the ideas from this answer and a comment to this question to create a new instance of a type extracted from a type list. The index of the desired type within the type list is provided at run time, only.

#include <iostream>
#include <memory>

class Base
{
public:
    virtual void foo() = 0;
};

class A : public Base
{
public:
    void foo() override
    {
        std::cout << "A" << std::endl;
    }
};

class B : public Base
{
public:
    void foo() override
    {
        std::cout << "B" << std::endl;
    }
};

template <class... Types>
class type_list {};

using list_t = type_list<A,B>;

template <class... Types>
static constexpr std::size_t length = sizeof...(Types);

template <std::size_t idx, class... Types>
class extract
{
    static_assert(idx < sizeof...(Types), "index out of bounds");

    template <std::size_t i, std::size_t n, class... Rest>
    struct extract_impl;

    template <std::size_t i, std::size_t n, class T, class... Rest>
    struct extract_impl<i, n, T, Rest...>
    {
        using type = typename extract_impl<i + 1, n, Rest...>::type;
    };

    template <std::size_t n, class T, class... Rest>
    struct extract_impl<n, n, T, Rest...>
    {
        using type = T;
    };

public:
    using type = typename extract_impl<0, idx, Types...>::type;
};

template <std::size_t idx, class TypeList>
struct type_list_extract;

template <std::size_t idx, template <class...> class TypeList, class... Types>
struct type_list_extract<idx, TypeList<Types...>>
{
    using type = typename extract<idx, Types...>::type;
};

template <std::size_t idx, class TypeList>
using type_list_extract_t = typename type_list_extract<idx, TypeList>::type;

template<std::size_t idx, bool done = false, class... Types>
struct action_wrapper
{
    static std::unique_ptr<Base> get_instance_by_id(std::size_t id)
    {
        if (id == idx)
        {
            using type = type_list_extract_t<idx, Types...>;
            return std::make_unique<type>();
        }

        static constexpr auto cont = length<Types...> < idx + 1;
        return action_wrapper<idx + 1, cont, Types...>::get_instance_by_id(id);
    }
};

template<std::size_t idx, class... Types>
struct action_wrapper<idx, true, Types...>
{
    static std::unique_ptr<Base> get_instance_by_id(std::size_t id)
    {
        return nullptr;
    }
};

static std::unique_ptr<Base> get_instance_by_id(std::size_t id)
{
    return action_wrapper<0, false, list_t>::get_instance_by_id(id);
}

int main()
{
    std::unique_ptr<Base> ptr = get_instance_by_id(1);
    if (ptr)
    {
        ptr->foo();
    }
}
\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

Prefer using \n over std::endl


Other than that, it looks like your code is rather clean and up-to-date style-wise. I might suggest that the get_instance_by_id return the actual type, not the Base in case it gets used in a context where a specific one is known at compile time (or within other template code).

\$\endgroup\$
5
  • \$\begingroup\$ I don't think it is possible to return a more specific type. When id == 0 the return type is A and if id == 1 the return type is B. But A and B have nothing in common but their parent class Base. Am I missing something? \$\endgroup\$
    – sigy
    Jul 11, 2018 at 10:10
  • \$\begingroup\$ Yes, the common Base is still there. You can provide more specific information in the return type for those that know about it, but code that expects a Base* will work just fine. Lookup co-variant return types in virtual functions. \$\endgroup\$
    – JDługosz
    Jul 11, 2018 at 19:17
  • \$\begingroup\$ But there are no virtual functions involved? In my get_instance_by_id one path returns an A the other path returns a B so the method's overall return type must be the a common type of both. So it must be a Base. Am I wrong? \$\endgroup\$
    – sigy
    Jul 11, 2018 at 19:29
  • \$\begingroup\$ The individual concrete get_instance_by_id functions return A* or B* as they are implemented. Code that calls generic get_instance_by_id can store the result in a Base*, as you have now. But you have the additional feature that some other code that does know the exact type can use it, without downcasting. \$\endgroup\$
    – JDługosz
    Jul 11, 2018 at 20:48
  • \$\begingroup\$ But I don't see how this could work. Could you show me? What do I need to change? \$\endgroup\$
    – sigy
    Jul 11, 2018 at 20:53

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.