Search code examples
c++templatesvariadic-templatesparameter-pack

How to construct different object according to their types in a template function?


Here is my question, I have a base class Base, two derived classes ClassA and ClassB form Base taking only two arguments, and two classes ClassB0 and ClassB1 derived from ClassB taking three arguments. I want to create class intance accroding to the type passed to the function create, if the type is derived from ClassB, the first argument will be filled with 10. The compiler always warn that there is no matching constructors.

#include <iostream>
#include <type_traits>

class Base {};
class ClassA : public Base {
public:
  ClassA(int a, int b) : Base() {
    std::cout << "ClassA: " << a << " " << b << "\n\n";
  }
};

class ClassB : public Base {
public:
  ClassB(int a, int b, int c) : Base() {
    std::cout << "ClassB: " << a << " " << b << " " << c << "\n";
  }
};

class ClassB0 : public ClassB {
public:
  ClassB0(int a, int b, int c) : ClassB(a, b, c) { 
    std::cout << "ClassB0: " << a << " " << b << " " << c << "\n\n";
  }
};
class ClassB1 : public ClassB {
public:
  ClassB1(int a, int b, int c) : ClassB(a, b, c) {
    std::cout << "ClassB1: " << a << " " << b << " " << c << "\n\n";
  }
};

template <typename T, typename ...Args>
T* create(Args&&... args) {
  T* comp = nullptr;

  if (std::is_base_of<ClassB, T>::value) {
    std::cout << "True ";
    comp = new T(10, std::forward<Args>(args)...);
  } else {
    std::cout << "False ";
    comp = new T(std::forward<Args>(args)...);
  }

  return comp;
}

int main() {
  create<ClassA>(1, 2);
  create<ClassB0>(2, 3);
  create<ClassB1>(2, 3);
}

Solution

  • The problem is that both the branch of if and else need to be evaluated at compile-time, despite of which one would be evaluated at run-time.

    You can apply constexpr if statement (since C++17), for which the condition must be known at compile-time, and either the if or else branch would be discarded and won't be evaluated at compile-time again.

    If the value is true, then statement-false is discarded (if present), otherwise, statement-true is discarded.

    template <typename T, typename ...Args>
    T* create(Args&&... args) {
      T* comp = nullptr;
    
      if constexpr (std::is_base_of<ClassB, T>::value) {
      // ^^^^^^^^^
        std::cout << "True ";
        comp = new T(10, std::forward<Args>(args)...);
      } else {
        std::cout << "False ";
        comp = new T(std::forward<Args>(args)...);
      }
    
      return comp;
    }
    

    LIVE

    Before C++17, you can apply overloading with SFINAE. e.g.

    template <typename T, typename ...Args>
    typename std::enable_if<std::is_base_of<ClassB, T>::value, T*>::type
    create(Args&&... args) {
      T* comp = nullptr;
      std::cout << "True ";
      comp = new T(10, std::forward<Args>(args)...);
      return comp;
    }
    template <typename T, typename ...Args>
    typename std::enable_if<!std::is_base_of<ClassB, T>::value, T*>::type
    create(Args&&... args) {
      T* comp = nullptr;
      std::cout << "False ";
      comp = new T(std::forward<Args>(args)...);
      return comp;
    }
    

    LIVE