26

Suppose I have a template of a function, say

template<typename T>
func(T a, T b, ...) {
  ...
  for (const auto &single : group) {
    ...
    auto c = GivenFunc1(a, b, single, ...);
    ...      }
  ...
}

However, for T being a special type, say "SpecialType", I want c being calculated by "GivenFunc2" rather than "GivenFunc1". However, I would not like to write a specialization for "SpecialType", since there will be a huge code duplication. So I want the template function being something like

template<typename T>
func(T a, T b, ...) {
  ...
  for (const auto &single : group) {
    ...
    auto c = (T == SpecialType) ? GivenFunc2(a, b, single, ...)
                                : GivenFunc1(a, b, single, ...);
    ...      }
  ...
}

Of course, this code does not compile since "T == SpecialType" is not valid. So how do I write it in an elegant way?

gyre
  • 16,369
  • 3
  • 37
  • 47
dudulu
  • 371
  • 3
  • 5
  • 4
    What about writing a template specialization instead of? – πάντα ῥεῖ Aug 19 '18 at 09:20
  • 1
    Possible duplicates: [1](https://stackoverflow.com/questions/13654309/can-a-template-function-compare-the-two-typenames) [2](https://stackoverflow.com/questions/14635413/how-to-check-if-two-template-parameters-are-exactly-the-same) [3](https://stackoverflow.com/questions/13071340/how-to-check-if-two-types-are-same-at-compiletimebonus-points-if-it-works-with) [4](https://stackoverflow.com/questions/39664316/check-if-two-types-are-equal-in-c) – user202729 Aug 20 '18 at 08:33

5 Answers5

32

It's as simple as:

auto c = std::is_same_v<T, SpecialType> ? GivenFunc2(a, b, single, ...)
                                        : GivenFunc1(a, b, single, ...);

If you can't use C++17, replace std::is_same_v<...> with std::is_same<...>::value.

But for this approach to work, both function calls have to be valid for every T you want to use, even if in reality one of them won't be executed.


If it's not the case, you can resort to if constexpr:

your_type_here c;
if constexpr (std::is_same_v<T, SpecialType>)
    c = GivenFunc2(a, b, single, ...);
else
    c = GivenFunc1(a, b, single, ...);

(This works only in C++17.)

Toby Speight
  • 27,591
  • 48
  • 66
  • 103
HolyBlackCat
  • 78,603
  • 9
  • 131
  • 207
  • 8
    I'd recommend using `if constexpr` either way. Sure, the compiler should be able to optimise away the check, but checking the validity of the other function call can be costly, especially if it involves additional template instantiations. Also, not using `if constexpr` can be a maintenance problem for those functions: anyone modifying them will have to take into account that they need to be valid even for arguments for which they'll never be called. –  Aug 19 '18 at 09:29
  • Great answer, lovely 6 votes. Unfortunately, as much as I would like to see this work on the compiler I'm using, it doesn't work. Is this a C++17 template/function? – TrebledJ Aug 19 '18 at 09:46
  • 3
    @JohnLaw Yes, `std::is_same_v` is C++17. If you don't have it, use `std::is_same<...>::value` instead. `if constexpr` is C++17 too, check other answers for alternatives. – HolyBlackCat Aug 19 '18 at 09:53
18

If you can use C++17, you can achieve the result in a very clean way (with constexpr and is_same):

template<typename T>
func(T a, T b, ...) {
  // ...

  if constexpr (std::is_same_v<T, SpecialType>) {
    // call GivenFunc2
  } else {
    // call GivenFunc1
  } 

  // ...
}

Pre C++17 you can achieve the same result using techniques such as SFINAE or "TAG Dispatching".

Additionally, you can just specialize the portion of the code referring to the function call (easy and avoid code duplication).

A short example here:

template <typename T>
struct DispatcherFn {
  auto operator()(const T&, int) {
      // call GivenFunc1
  }
};

template <>
struct DispatcherFn<SpecialType> {
  auto operator()(const SpecialType&, int) {
    // GivenFunc2
  }
};

template <typename T>
void func(const T& t) {
  // ... code ...
  auto c = DispatcherFn<T>()(t, 49);  // specialized call
}
BiagioF
  • 9,368
  • 2
  • 26
  • 50
12

You can always use template specializations instead of doing type comparisons of template parameters. Here's a simplified, working example:

#include <iostream>
#include <string>

template<typename T>
int GivenFunc1(T a, T b) {
     std::cout << "GivenFunc1()" << std::endl;
     return 0;
}

template<typename T>
int GivenFunc2(T a, T b) {
     std::cout << "GivenFunc2()" << std::endl;
     return 1;
}

template<typename T>
void func(T a, T b) {
    auto c = GivenFunc2(a, b);
    std::cout << c << std::endl;
}

template<>
void func(std::string a, std::string b) {
    auto c = GivenFunc1(a, b);
    std::cout << c << std::endl;
}

int main() {
    func(2,3);
    std::string a = "Hello";
    std::string b = "World";
    func(a,b);
}

See it working online here.

πάντα ῥεῖ
  • 1
  • 13
  • 116
  • 190
6

In the best solution is if constexpr.

In this works:

template<class V>
auto dispatch( V const& ) {
  return [](auto&&...targets) {
    return std::get<V{}>( std::forward_as_tuple( decltype(targets)(targets)... ) );
  };
}

then:

 auto c = dispatch( std::is_same<T, SpecialType>{} )
 (
   [&](auto&& a, auto&& b){ return GivenFunc2(a, b, single...); },
   [&](auto&& a, auto&& b){ return GivenFunc1(a, b, single, ...); }
 )( a, b );

does what you want. (It is also a function which returns a function which returns a function)

Live example.

dispatch picks one of the two lambdas and returns it at compile time. We then call the picked lambda with a and b. So only the valid one is compiled with a type for a and b.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
1

Convert GivenFunc1 to a functor and specialise that.

template <class T>
class GivenFunc
{
    X operator()(T a, T b, Y single)
    {
        ...
    }
}

template <>
class GivenFunc<SpecialType>
{
    X operator()(SpecialType a, SpecialType b, Y single)
    {
        ...
    }
}

Then you can say

auto c = GivenFunc<T>()(a, b, single);
john
  • 85,011
  • 4
  • 57
  • 81