Building function-pointer table with lambdas

Started by
3 comments, last by Zipster 6 years, 5 months ago

So while refactoring my code that uses functors into lambdas, I came across a specific function-pointer table used for sorting that I'm struggling to get right. Thats the current code


// some functors:
struct FrontToBackSorter
{
  inline bool operator()(const SortData& left, const SortData& right) const
  {
    return left.z > right.z;
  }
};

struct BackToFrontSorter
{
  inline bool operator()(const SortData& left, const SortData& right) const
  {
    return left.z < right.z;
  }
};

struct TextureSorter
{
  inline bool operator()(const SortData& left, const SortData& right) const
  {
    return left.pTexture < right.pTexture;
  }
};

// the table, sort-function etc...

template<typename Sorter>
void sortFunction(SortingVector& vSortData)
{
  std::stable_sort(vSortData.begin(), vSortData.end(), Sorter());
}

using SortFunction = void (*)(SortingVector&);

constexpr SortFunction sortFunctions[] =
{
  &sortFunction<BackToFrontSorter>, &sortFunction<FrontToBackSorter>, &sortFunction<TextureSorter>
    ...
};

Now as I said I would like to replace those functors with lambdas, but since I can't store the lambda itself but another somehow generate the code for "sortFunction" with the lambda instead of Sorter, I really don't know how to pull this off (storing the sort-function & calling std::stable_sort with the adress is not an option since it absolutely has to be inlined). Thats the closest I got so far:


template<typename Sorter>
  void sortFunction(SortingVector& vSortData)
{
  std::stable_sort(vSortData.begin(), vSortData.end(), Sorter()); // error: lambda default-ctor deleted
}

constexpr auto generateSortFunctions(void)
{
  constexpr auto frontToBack = [](const SortData& left, const SortData& right)
  {
    return left.z > right.z;
  };

  constexpr auto backToFront = [](const SortData& left, const SortData& right)
  {
    return left.z < right.z;
  };

  constexpr auto texture = [](const SortData& left, const SortData& right)
  {
    return left.pTexture < right.pTexture;
  };

  using SortFunction = void(*)(SortingVector& vSortData);

  constexpr std::array<SortFunction, 3> vFunctions =
  {
    &sortFunction<decltype(backToFront)>,
    &sortFunction<decltype(frontToBack)>,
    &sortFunction<decltype(texture)>,
  };

  return vFunctions;
}

Unfortunately this doesn't work since you cannot instantiate a lambda directly.

So, any ideas how this could work? Is this even possible (without lots of redundant work like writing a second lambda that includes the sort for every instantiation? std::function (which I suppose could work) & type-erasure should also be avoided if possible, since this is performance-critic code.

Thanks!

Advertisement

I think this is as close as it's going to get:


using SortVector = std::vector<int>;
using Sorter = std::function<bool(int lhs, int rhs)>;
using SortFunction = std::function<void(SortVector& data)>;

SortFunction generateSortFunction(Sorter sorter)
{
  return [=](SortVector& data) { std::stable_sort(data.begin(), data.end(), sorter); };
}

std::array<SortFunction, 2> sorters =
{
    generateSortFunction([](int lhs, int rhs) { return lhs < rhs; }),
    generateSortFunction([](int lhs, int rhs) { return lhs > rhs; })
};

 

Ah, the generateSortFunction-idea is just what I was looking for, thanks :) Unfortunately I really cannot/do not want to use std::function here (aside from its overhead it also disallowes constexpr here too), and while I could simply template the generateSortFunction, it would still require me to store the result as std::function due to the capture. I think I found a "better" solution though:
 


using CompareFunction = bool(*)(const SortData& left, const SortData& right);

// make the generateSortFunction templated with the adress of our Sort-function as non-type argument
template<CompareFunction Sorter>
  constexpr auto generateSortFunction(void)
{
  return [](SortingVector& vData)
  {
    std::stable_sort(vData.begin(), vData.end(), Sorter);
  };
}

constexpr auto generateSortFunctions(void)
{ 
  // we can just store the lambda directly as a function-pointer
  constexpr CompareFunction frontToBack = [](const SortData& left, const SortData& right)
  {
    return left.z > right.z;
  };

  constexpr CompareFunction backToFront = [](const SortData& left, const SortData& right)
  {
    return left.z < right.z;
  };

  constexpr CompareFunction texture = [](const SortData& left, const SortData& right)
  {
    return left.pTexture < right.pTexture;
  };

  using SortFunction = void(*)(SortingVector& vSortData);

  // now those function-pointers can be used to statically generate different overload of generateSortFunction
  constexpr std::array<SortFunction, 3> vFunctions =
  {
    generateSortFunction<backToFront>(),
    generateSortFunction<frontToBack>(),
    generateSortFunction<texture>(),
  };

  return vFunctions;
}

I'm storing the lambda directly inside a function-pointer and pass that to the generateSortFunction as a non-type template parameter. Wohoo for modern C++ :D I'll just have to check whether this actually allows the compiler to inline the sorting-function into std::stable_sort, but I'm pretty certain it can.

Ah yes, I suppose that if the capture is unnecessary you can decay the lambdas down to function pointers :)

This topic is closed to new replies.

Advertisement