Jump to content
  • Advertisement
Juliean

Building function-pointer table with lambdas

Recommended Posts

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!

Share this post


Link to post
Share on other sites
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; })
};

 

Share this post


Link to post
Share on other sites

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.

Share this post


Link to post
Share on other sites

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

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

Participate in the game development conversation and more when you create an account on GameDev.net!

Sign me up!