c++: Handle different template parameters separately

Viewed 79

I have a program as follows (It's a recreation of my actual issue):

#include <iostream>

class A {
public:
    void printA () {
        std::cout << "AA" << std::endl;
    }
};

class B {
public:
    void printB () {
        std::cout << "BB" << std::endl;
    }
};

template <typename T>
class C {
public:
    void printC () {
        T obj;
        if (std::is_same<A, T>::value) {
            obj.printA ();
        } else if (std::is_same<B, T>::value) {
            obj.printB ();
        }
    }
};


int main () {
    C<A> c1;
    C<B> c2;
    c1.printC ();
    c2.printC ();
    return 0;
}

On compilation, I am getting the following errors:

handle_different_typenames.cpp: In instantiation of ‘void C<T>::printC() [with T = A]’:
handle_different_typenames.cpp:34:16:   required from here
handle_different_typenames.cpp:25:17: error: ‘class A’ has no member named ‘printB’; did you mean ‘printA’?
   25 |             obj.printB ();
      |             ~~~~^~~~~~
      |             printA
handle_different_typenames.cpp: In instantiation of ‘void C<T>::printC() [with T = B]’:
handle_different_typenames.cpp:35:16:   required from here
handle_different_typenames.cpp:23:17: error: ‘class B’ has no member named ‘printA’; did you mean ‘printB’?
   23 |             obj.printA ();
      |             ~~~~^~~~~~
      |             printB

How to solve this issue? Ideally, I want the situation where if T = A, the code inside else if (std::is_same<B, T>::value) {...} should not be checked at compile time. Similarly, if T = B, the code inside if (std::is_same<A, T>::value) {...} should not be checked at compile time.

Thanks in advance!

2 Answers

There are several ways...

In C++17 the best if you only need it in one place is:

template <typename T>
class C {
public:
    void printC () {
        T obj;
        if constexpr (std::is_same<A, T>::value) {
            obj.printA ();
        } else if constexpr (std::is_same<B, T>::value) {
            obj.printB ();
        }
    }
};

In older standards overloading is an easy one for this case:

void print(A& obj) { obj.printA(); }
void print(B& obj) { obj.printB(); }

template <typename T>
class C {
public:
    void printC () {
        T obj;
        print(obj);
    }
};

In you should use if constexpr:

template <typename T>
class C {
public:
    void printC () {
        T obj;
        if (std::is_same<A, T>::value) {
            obj.printA ();
        } else if (std::is_same<B, T>::value) {
            obj.printB ();
        }
    }
};

prior to that you can fake it

template<class X, class A0, class...Args>
auto magic_switch( std::integral_constant<X,0>, A0&& a0, Args&&... ) {
  return std::forward<A0>(a0);
}
template<class X, X x, class A0, class...Args>
auto magic_switch( std::integral_constant<X,x>, A0&& a0, Args&&...args ) {
  return magic_switch( std::integral_constant<X, x-1>, std::forward<Args>(args)... );
}

with this helper function.

Your code becomes:

void printC () {
    T obj;
    magic_switch(
      std::is_same<A, T>{},
      // false:
      [&](auto& obj){
        obj.printB();
      },
      // true:
      [&](auto& obj){
        obj.printA();
      })
      ( obj ); // pass in obj as the first argument
}

this version is . Here, we return a different function object depending on the compile time truth value of std::is_same.

You can also write:

template<class F, class...Ts>
void run_if( std::true_type, F&& f, Ts&&... ts ) {
  f(std::forward<Ts>(ts)...);
}
template<class...Xs>
void run_if( std::false_type,Xs&&...) {}
void printC () {
  T obj;
  run_if( std::is_same<A,T>{}, [&](auto& obj){obj.printA();}, obj );
  run_if( std::is_same<B,T>{}, [&](auto& obj){obj.printB();}, obj );
}

note that you must pass obj as an auto parameter (and anything depending on the is_same type test to determine if it is valid) to avoid an ill-formed program, no diagnostic required.


The further back in standard versions you go, the harder this gets. I don't think it is possible to do it "in-line" in for example; there, you'd have to have a helper type or function. An overload is simplest:

void print( A const& a ) { a.printA(); }
void print( B const& b ) { b.printB(); }

void printC () {
    T obj;
    print(obj);
}

but you can get fancy with specialized helper templates or whatever.


The magic switch/run if style is something I have done to retrofit old MSVC code with its extensions to work in a standard compliant manner without having to restructure the code.

Related