Przemysław Czechowski
Przemysław Czechowski

Reputation: 776

constexpr parametrized function pointer

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

Answers (3)

Barry
Barry

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

max66
max66

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

Barry
Barry

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

Related Questions