Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 669
Przemysław Czechowski Avatar asked Jan 23 '19 14:01

Przemysław Czechowski


People also ask

Can a pointer be constexpr?

C++'s constexpr brings another new dimension to the problem too! It behaves like const in the sense that it makes all pointers constant pointers. But because it occurs at the start of your statement (rather than after the '*') its not immediately obvious.

How do I know if a function is constexpr?

The easiest way to check whether a function (e.g., foo ) is constexpr is to assign its return value to a constexpr as below: constexpr auto i = foo(); if the returned value is not constexpr compilation will fail.

Can std :: function be constexpr?

Constexpr constructors are permitted for classes that aren't literal types. For example, the default constructor of std::unique_ptr is constexpr, allowing constant initialization.


1 Answers

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.

like image 107
Barry Avatar answered Oct 26 '22 23:10

Barry