0

I have a templated function, say, a sort function like this

template <typename Iter>
void sort1(Iter first, Iter last) {...}

template <typename Iter, typename Comp>
void sort1(Iter first, Iter last, Comp comp) {...}

where Iter could be an iterator or a pointer.

I have another templated function that uses distinct Comps in different cases:

template <typename Iter>
void func_using_sort(Iter first, Iter last, int cond) {
  if (cond == 0) {
    sort1(first, last, [](const auto& a, const auto& b) {
      // code 1
    });
  } else if (cond == 1) {
    sort1(first, last, [](const auto& a, const auto& b) {
      // code 2
    });
  } else if (cond == 2) {
    sort1(first, last);
  }
}

Now I would like to generalize func_using_sort to use any sort function that has the same interface as sort1, say,

template <typename Iter>
void sort2(Iter first, Iter last) {...}
template <typename Iter, typename Comp>
void sort2(Iter first, Iter last, Comp comp) {...}

Is there an elegant way to achieve this?

user69818
  • 403
  • 6
  • 13
  • Which version of c++ are you using? – Homer6 Apr 12 '20 at 20:06
  • @Homer6 C++14, but if C++17 is required I can also switch to it. – user69818 Apr 12 '20 at 20:11
  • 2
    Does this answer your question? [Template function as a template argument](https://stackoverflow.com/questions/4697180/template-function-as-a-template-argument) – cigien Apr 12 '20 at 20:39
  • @cigien Thanks for the link. I did try to do templated functors but was not sure how to deal with the lambdas (also please see my new edits for a more complicated situation) – user69818 Apr 12 '20 at 23:07
  • @cigien After many tries, I finally figure out how to use functors to achieve it (and without using template template argument). See my posted answer. – user69818 Apr 12 '20 at 23:56

2 Answers2

1

You can in principle just pass a function object as you do for comp:

template <typename Iter, typename Sort>
void func_using_sort(Iter first, Iter last, Sort sort, int cond) {
  if (cond == 0) {
    sort(first, last, [](const auto& a, const auto& b) {
      // code 1
    })
  } else if (cond == 1) {
    sort(first, last, [](const auto& a, const auto& b) {
      // code 2
    })
  }
}

The question is how you would pass a function template or an overload set of functions to that function. There is at the moment no really clean solution to this problem, but the following works since C++14 using a generic lambda:

func_using_sort(first, last, [](auto... args){ return sort2(args...); }, cond);

A more generic version of this pattern which does perfect forwarding, is SFINAE-friendly and takes care of other edge cases can be found in e.g. paper P1170 to the C++ standards committee:

#define FWD(x) static_cast<decltype(x)&&>(x)
#define RETURNS(expr) noexcept(noexcept(expr)) -> decltype(expr) { return expr; }
#define OVERLOADS_OF(name) [&](auto&& ...args) RETURNS(name(FWD(args)...))

with which it would then become

func_using_sort(first, last, OVERLOADS_OF(sort2), cond);

Alternatively you can wrap your sort2 function in a functor class, in which case you can pass it directly to the function defined above. At the moment there is sadly no way to pass function templates or overload sets directly as function arguments. That is what the paper mentioned above and other papers try to address.

walnut
  • 21,629
  • 4
  • 23
  • 59
0

@walnut Provided an excellent answer. But I just figured out the way to use functors as well which I couldn't previously (and without using any template template argument). I am posting it here for future reference.

template <typename Iter>
struct Sort1 {
  template <typename Comp>
  void operator()(Iter first, Iter last, Comp comp) {
    sort1(first, last, comp);
  }

  void operator()(Iter first, Iter last) {
    sort1(first, last);
  }
};

template <typename Sort, typename Iter>
void func_using_sort(Sort sort, Iter first, Iter last, int cond) {
  if (cond == 0) {
    sort(first, last, [](const auto& a, const auto& b) {
      // code 1
    });
  } else if (cond == 1) {
    sort(first, last, [](const auto& a, const auto& b) {
      // code 2
    });
  } else if (cond == 2) {
    sort(first, last);
}

template <typename Iter>
void func_using_sort1(Iter first, Iter last, int cond) {
  func_using_sort(Sort1<Iter>(), first, last, cond);
}
user69818
  • 403
  • 6
  • 13
  • 1
    You don't need the template parameter on `Sort1`. You can just give the `operator()` overloads the `Iter` template parameter (as you did in your question). The you won't need to specify `Sort1()`, but you can use `Sort1{}` instead directly. – walnut Apr 12 '20 at 23:58