3

I know, there are several topic, asking very close things but I don't get it to work in my case.

I would like to build a templated factory with index access during runtime. Therefore I have several types with the same base type. The factory gets the types which it is able to procude per template parameters. The call to the factory just gives an index. This is a small example:

#include <iostream>
#include <memory>
#include <tuple>

struct Base {
};

struct A : Base {
  A(int) { std::cout << "A" << std::endl; }
};
struct B : Base {
  B(int) { std::cout << "B" << std::endl; }
};
struct C : Base {
  C(int) { std::cout << "C" << std::endl; }
};

template <typename ... Types>
struct Factory {
  typedef std::tuple<Types...> TypesTuple;

  std::shared_ptr<Base> operator ()(int index) {
    return produce(index);
  }

  std::shared_ptr<Base> produce(int index) {
    switch (index) {
    case 0: return std::make_shared<typename std::tuple_element<0, TypesTuple>::type>(42);
    case 1: return std::make_shared<typename std::tuple_element<1, TypesTuple>::type>(42);
    }
    throw; 
  }
};

//==============================================================================
int main() {    
  Factory<A, C> factory_ac;
  auto a1 = factory_ac(0);
  auto c1 = factory_ac(1);

  Factory<A, B, C> factory_bc;
  auto a2 = factory_bc(0);
  auto b2 = factory_bc(1);
  auto c2 = factory_bc(2);
}

I tried to overload the produce method with

template <typename = typename std::enable_if<std::tuple_size<TypesTuple>::value==2>::type>

counting up the size and providing the respective switch statements, but this does not compile, overload not allowed.

I tried using https://stackoverflow.com/a/7383493/2524462 but I couldn't get it to work, because the parameter packs don't expand with a lambda and wrapping it in a template function I get problems with the constexpr array, since I don't have trivial types.

Boost MPL for_eachcomes to mind, but I got problems compiling, because my types are not trivially constructable.

So how would one change the factory to get the main to compile and work?

Community
  • 1
  • 1
Mike M
  • 2,263
  • 3
  • 17
  • 31
  • Do I understand you goal correct that you want to have a factory which depending on an integer argument just creates an object of the corresponding type from the factory's template argument list? (well, actually a `std::shared_ptr` pointing to an object of the corresponding type) – Dietmar Kühl Dec 14 '13 at 20:58

2 Answers2

4

It seems this can be done quite straight forward:

template <typename T>
std::shared_ptr<Base> make() {
    return std::make_shared<T>();
}
template <typename... T>
class Factory {
public:
    std::shared_ptr<Base> operator()(int index) {
        static constexpr std::shared_ptr<Base> (*factories[])() = {
            &make<T>...
        };
        if (index < 0 && sizeof...(T) <= index) {
            throw std::range_error("type index out of range");
        }
        return (factories[index])();
    }
};

I'm currently not in the position to compile the code but something along this lines should work: the idea is to create an array of factory functions and just call into this array.

Dietmar Kühl
  • 150,225
  • 13
  • 225
  • 380
  • I like your solution a lot more than mine. Very nice. – Simple Dec 14 '13 at 21:18
  • 1
    @DyP: Well, in the array I don't think I can use `&std::make_shared...` because the elements would all have different types which are not compatible. However, I think I can use `std::make_shared()` in the `make()` function (and changed the answer accordingly). Thanks for fixing the errors! – Dietmar Kühl Dec 14 '13 at 21:25
  • I think a small improvement could be to make the array `constexpr`, or at least `const`. – dyp Dec 14 '13 at 21:28
  • @DyP: hm. yes, I suppose that's true. Now the question becomes where I need to put the `constexpr` :) I think it goes after the `*`. – Dietmar Kühl Dec 14 '13 at 21:32
  • @DietmarKühl it would go in place of `static`. – Simple Dec 14 '13 at 21:34
  • @DietmarKühl :) Indeed I was wondering the same and put it just after the `static`.. they're both *decl-specifiers*.. but I think it's time for an alias ;) – dyp Dec 14 '13 at 21:34
  • @Simple: I'm pretty sure it _does_ not go after the `static` (it is the [wrong location](http://kuhllib.com/2012/01/17/continental-const-placement/) anyway)! That would make the `std::shared_ptr` constant. I don't think we are interested in doing so. The question is whether it goes before the `*` but I don't think it does because the pointed to function is already constant anyway. However, making the pointers `const` actually makes some sense and using a `typedef` would put it there, too... – Dietmar Kühl Dec 14 '13 at 21:39
  • @DietmarKühl it does go where you place `static`. `constexpr` isn't like `const`. [See here](http://ideone.com/NtpPpN). – Simple Dec 14 '13 at 21:41
  • @Simple: I don't dispute that it _can_ go there but it has the wrong semantics: the goal is clearly to make the pointers `constexpr`s. Admittedly `constexpre` is different to `const` but I think they apply to the same entities... – Dietmar Kühl Dec 14 '13 at 21:44
  • @DietmarKühl it does make the pointers `const`. You don't have a "`constexpr` type". – Simple Dec 14 '13 at 21:45
  • I think `constexpr` must be placed differently compared to `const`: clang++ allows `static int i; constexpr int* x = &i; *x = 42;` but not `static int i; const int* x = &i; *x = 42;` The reason, I think, is that `constexpr` applies to an *object declaration*, not to a type. (And a const array is an array of const) – dyp Dec 14 '13 at 21:48
  • @Simple, @DyP: OK, I'll need to read up on where `constexpr` goes. I have edited the code to have to after `static`. – Dietmar Kühl Dec 14 '13 at 21:58
  • @Simple, Dietmar Kühl: Thank you both very much, works as expected. – Mike M Dec 14 '13 at 22:22
2

If I have understood your requirements correctly, I think this does what you want:

template<int... Is>
struct indices { typedef indices type; };

template<int N, int... Is>
struct make_indices : make_indices<N - 1, N - 1, Is...> { };

template<int... Is>
struct make_indices<0, Is...> : indices<Is...> { };

template<typename... Types>
struct Factory
{
    typedef std::tuple<Types...> TypesTuple;

    std::shared_ptr<Base> operator()(int const index)
    {
        return produce(index);
    }

    std::shared_ptr<Base> produce(int const index)
    {
        return produce_impl(make_indices<sizeof...(Types)>(), index);
    }

    template<int I, int... Is>
    std::shared_ptr<Base> produce_impl(indices<I, Is...>, int const index)
    {
        if (I == index) {
            return std::make_shared<typename std::tuple_element<I, TypesTuple>::type>(42);
        }

        return produce_impl(indices<Is...>(), index);
    }

    std::shared_ptr<Base> produce_impl(indices<>, int const index)
    {
        throw "Uh-oh!";
    }
};

See output here.

Simple
  • 13,992
  • 2
  • 47
  • 47