Reputation: 776
I have the following third-party API:
using StatisticsFunc = double (*)(const std::vector<double> &)
libraryClass::ComputeStatistics(StatisticsFunc sf);
Which I'm using like this:
obj1->ComputeStatistics([](const auto& v) {return histogram("obj1", v);};
obj2->ComputeStatistics([](const auto& v) {return histogram("obj2", v);};
But all those lambdas are just repeated code. I'd rather have it like this:
obj1->ComputeStatistics(getHistogramLambda("obj1"));
So I need to define:
constexpr auto getHistogramLambda(const char* name) {
return [](const auto& v) {return histogram(name, v);};
}
But it won't work, because name
is not captured. Neither will this work:
constexpr auto getHistogramLambda(const char* name) {
return [name](const auto& v) {return histogram(name, v);};
}
Because capturing lambda is not stateless anymore and cannot be cast to function pointer.
Ofc one can do it as a macro, but I want a modern C++ 17 solution.
Passing string as template argument seems an option as well:
https://stackoverflow.com/a/28209546/7432927 , but I'm curious if there's a constexpr
way of doing it.
Upvotes: 8
Views: 495
Reputation: 303087
Different answer, courtesy of Michael Park. We can encode the value we want in a type - not passing the string literal we want as a function argument or a template argument, but as an actual type - and that way we don't need to capture it:
#define CONSTANT(...) \
union { static constexpr auto value() { return __VA_ARGS__; } }
#define CONSTANT_VALUE(...) \
[] { using R = CONSTANT(__VA_ARGS__); return R{}; }()
template <typename X>
constexpr auto getHistogramLambda(X) {
return [](const auto& v) { return histogram(X::value(), v);};
}
obj->ComputeStatistic(getHistogramLambda(CONSTANT_VALUE("obj1")));
obj->ComputeStatistic(getHistogramLambda(CONSTANT_VALUE("obj2")));
Not sure this is better than the UDL approach in this particular case, but it's an interesting technique for sure.
Upvotes: 1
Reputation: 66200
Not sure to understand what do you exactly need but... what about declaring a global constexpr
array of char const
pointers
constexpr std::array<char const *, 3u> arrStr {{"obj0", "obj1", "obj2"}};
then receiving in getHistogramLambda()
the index of the required string as template parameter?
template <std::size_t N>
constexpr auto getHistogramLambda () {
return [](const auto& v) {return histogram(arrStr.at(N), v);};
}
This way you can call ComputeStatistic()
as follows
obj1->ComputeStatistics(getHistogramLambda<1u>());
Upvotes: -1
Reputation: 303087
Sort of.
This:
obj1->ComputeStatistics(getHistogramLambda("obj1"));
Won't work for the reasons you point out - you need to capture state. And then, we can't write this:
obj1->ComputeStatistics(getHistogramLambda<"obj1">());
Because while we can have template parameters of type const char*
we can't have them bind to string literals. You could do it this way:
template <const char* name>
constexpr auto getHistogramLambda() {
return [](const auto& v) {return histogram(name, v);};
}
const char p[] = "obj1";
obj1->ComputeStatistics(getHistogramLambda<p>());
Which is pretty awkward because you need to introduce the extra variable for each invocation. In C++20, we'll be able to write a class type that has as its template paramater a fixed string, which will allow getHistogramLambda<"obj1">
to work, just in a slightly different way.
Until then, the best way currently is probably to use a UDL to capture the individual characters as template parameters of some class type:
template <char... Cs>
constexpr auto getHistogramLambda(X<Cs...>) {
static constexpr char name[] = {Cs..., '\0'};
return [](const auto& v) { return histogram(name, v);};
}
obj->ComputeStatistic(getHistogramLambda("obj1"_udl));
The intent here is that "obj"_udl
is an object of type X<'o', 'b', 'j', '1'>
- and then we reconstruct the string within the body of the function template in a way that still does not require capture.
Is this worth it to avoid the duplication? Maybe.
Upvotes: 3