Reputation: 2035
I'm am trying to pass a lambda (that captures local context by reference) to a function and ensure it is inlined. I think a solution would be to use a function template and a C++20 concept, but I don't know how to write the concept of a lambda that takes specific types.
The aim is to factorize a complex loops on raster files, and let the caller execute code for each pixel.
The lambda call must be inlined.
I had bad luck with:
std::function
: the performance with lambda is 2x worse than writing the whole loop in the caller code.Working code with bad performance:
// loop
template<typename T>
class Raster<T>
{
template<typename FileDataType>
void SaveToDisk(std::ostream& file, const std::function<FileDataType(T&)>& callback) const;
template<typename FileDataType>
void SaveToDisk(std::ostream& file, AnotherCallbackType...);
}
// call
class GeoRaster<double> : public Raster
{
void Test()
{
// Creates a file containing "short" values, from a GeoRaster<double>
SaveToDisk<short>(file, [&](double val)
{
// this lambda call must be inlined
if (val == this->nodata)
{
return 255;
}
else
{
return short(val) / 16;
}
}
}
});
Could you help me to write a concept like this:
template<typename T, typename FileDataType>
concept SimpleWriteCallback = requires(T a) {
{ a } -> std::convertible_to<std::function<FileDataType(T&)>>;
};
template<typename FileDataType, SimpleWriteCallback<FileDataType> Callback>
void SaveToDisk(std::ostream& file, Callback& callback) const;
Upvotes: 2
Views: 2587
Reputation: 743
I think that you are looking for is std::invocable, which is a concept that allows you to enforce that a type can be called with a given set of arguments:
#include <concepts>
#include <cstdio>
void do_stuff(std::invocable<int, int> auto &&fn) {
fn(3, 4);
}
int main() {
do_stuff([](const int a, const int b) { std::printf("%d %d\n", a, b); });
// do_stuff([] { std::puts("hi!"); }); // won't compile
return 0;
}
If you need to also constrain the return type of Func
, you may use a requires()
block to add a compound requirement:
template <typename Func>
requires requires(Func &&fn, std::vector<int> v) {
std::invocable<Func, const std::vector<int>&, bool>;
{ fn(v, false) } -> std::convertible_to<int>;
}
int do_stuff(std::vector<int> v, Func &&fn) {
v.push_back(31);
return fn(v, true);
}
int main() {
do_stuff({1, 2 ,3}, [](const auto &, bool) { return 3; }); // ok: right parameters, and return type is convertible to int
do_stuff({1, 2 ,3}, [](const auto &, bool) { return 3.f; }); // ok: float is also implicitly convertible to int
//do_stuff({1, 2 ,3}, [](const auto &v) { return 3; }); // won't compile, cannot be invoked with (const vector<int>&, bool)
//do_stuff({1, 2 ,3}, [](const auto &v, bool) { return ""; }); // won't compile, const char (&)[] is not convertible to int
}
Upvotes: 4
Reputation: 303097
First, we have to start with how we're even thinking about the constraint:
template<typename T, typename FileDataType>
concept SimpleWriteCallback = /* ... */;
We're not constraining the callback on a FileDataType
, FileDataType
is what the callback returns. Rather, we're constraining it on the type that we're passing into it:
template <typename F, typename T>
concept SimpleWriteCallback = std::invocable<F&, T&>;
And you'd then use this concept like:
template <SimpleWriteCallback<T> F>
void SaveToDisk(std::ostream& file, F&& callback) const {
using FileDataType = std::invoke_result_t<F&, T&>;
// ...
}
Now, separately, if you want to override the actual return type (as you seem to do, since the lambda returns an int
but you want a short
), you can provide an overload that lets you provide the type explicitly and have the other overload just forward to it:
template <typename FileDataType, SimpleWriteCallback<T> F>
requires std::convertible_to<std::invoke_result_t<F&, T&>, FileDataType>
void SaveToDisk(std::ostream& file, F&& callback) const {
// ...
}
template <SimpleWriteCallback<T> F>
void SaveToDisk(std::ostream& file, F&& callback) const {
// by default, FileDataType is the actual invocation result
SaveToDisk<std::invoke_result_t<F&, T&>>(file, callback);
}
Upvotes: 1