0

I have got kind of a weird use-case. Here's a very simplified version of it.

Let's say I have a class Base and classes DerivedOne and DerivedTwo which are derived from the Base class.

Then, there's a enumeration:

enum DerClasses {Derived1, Derived2};

and a function which would take a enum and return the instance of derived class, depending on the value.

Something like:

inline Base* create_instance(DerClasses enum_class){
    switch(enum_class) {
        case Derived1:
            return new Derived1();
        case Derived2:
            return new Derived2();
    }
}

Obviously this works, but only with the cast to the derived class afterwards.

Derived1 *derived_pointer = dynamic_cast<Derived1*>(pointer);

And I don't want users to make these dynamic casts themselves or even know anything about the classes.

Is that possible to somehow hide these casts and make an API with automatic type deduction, like

auto ptr = create_instance(DerClasses::Derived1);
ptr->derived1_class_only_variable = 123;
user3051029
  • 613
  • 1
  • 5
  • 19
  • 1
    You would have to rely on a template argument instead. But then that's only possible if you know at compile time which type you want to create. If not, for example if it depends on user input, solutions are much more complex and certainly not transparent to the user at all. If using the base type's interface is not acceptable, I'd reconsider if polymorphism is the right tool for the job. – François Andrieux Apr 03 '19 at 14:49
  • 4
    The whole point of polymorphism is that the interface should be in the base class and you don't care what derived object you actually have. If you don't do that then you need to cast which defeats the purpose. – NathanOliver Apr 03 '19 at 14:49

2 Answers2

3
template<DerClasses enum_class>
auto create_instance(){
  if constexpr (enum_class == DerClasses::Derived1) {
    return std::make_unique<Derived1>();
  else if constexpr (enum_class == DerClasses::Derived2) {
    return std::make_unique<Derived2>();
}

this is like . Doing this in is very annoying.

If you don't know the DerClasses value at compile time, this does not work, and cannot work. The closest you can get is continuation passing style:

template<class F>
decltype(auto) create_instance(DerClasses enum_class, F&& f){
  switch(enum_class) {
    case DerClasses::Derived1:
      return f(std::make_unique<Derived1>());
    case DerClasses::Derived2:
      return f(std::make_unique<Derived2>());
  }
}

which is used like:

create_instance(DerClasses::Derived1, [&](auto&& ptr) {
  if constexpr( std::is_same< std::decay_t<decltype(*ptr)>, Derived1 >{} )
    ptr->derived1_class_only_variable = 123;
});

but that sucks too, because the lambda is invoked with both derived types; only one is run.

A use of override could work, but again you are getting crazy.

Yakk - Adam Nevraumont
  • 235,777
  • 25
  • 285
  • 465
2

You can do it by using some meta-programming. If you are using C++ 11/14 you can do something like this:

enum class ClassType {
    FirstType,    
    SecondType,
    ThirdType
};

 namespace internal {

    template <ClassType Type>
    struct _build_type {};

    template <>
    struct _build_type<ClassType::FirstType> {
        constexpr auto operator()() {
            return std::make_unique<MyFirstType>();
        }
    };

    template <>
    struct _build_type<ClassType::SecondType> {
        constexpr auto operator()() {
            return std::make_unique<MySecondType>();
        }
    };

    template <>
    struct _build_type<ClassType::ThirdType> {
        constexpr auto operator()() {
            return std::make_unique<MyThirdType>();
        }
    };

} 

template <WindowType Type>
constexpr auto create_instance() {
    return internal::_build_type<Type>{}();
}

And then your code becomes exactly, what you are saying:

auto ptr = create_instance<ClassType::FirstType>();
ptr->derived1_class_only_variable = 123;

In modern C++17, you can simplify the code with a simple constexpr if condition.

mohabouje
  • 3,457
  • 1
  • 13
  • 26