8

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.

Barry
  • 286,269
  • 29
  • 621
  • 977
  • If you're not capturing the string by value, then what is meant to stop a user from passing a string that *isn't* a literal, one whose lifetime may end before the callback gets called? – Nicol Bolas Jan 23 '19 at 14:54
  • Could you possibly change `StatisticsFunc` to `std::function&)>`? – aschepler Jan 23 '19 at 14:58
  • Template seems like the way to go, I doubt that it is doable with some constexpr cleverness. – paler123 Jan 23 '19 at 15:12
  • @aschepler this is a part of the third party API, I cannot change it. – Przemysław Czechowski Jan 23 '19 at 15:31
  • @NicolBolas well, the string is passed to `constexpr` function, so I'd like to enforce it being known at compile time. – Przemysław Czechowski Jan 23 '19 at 15:34
  • @PrzemysławCzechowski: `constexpr` functions do not *have* to be executed at compile-time. – Nicol Bolas Jan 23 '19 at 15:35
  • Are you really taking a pointer to a pointer to a function? I feel like there's an extra `*` - I went ahead and removed it – Barry Jan 23 '19 at 15:37
  • Are there any reason for using lambas. What about old school i.e. something like obj1->ComputeStatistics([std::bind](https://en.cppreference.com/w/cpp/utility/functional/bind)(histogram,"obj1",_1)) – Victor Gubin Jan 23 '19 at 15:46
  • @VictorGubin Need to end up with a function pointer, so that won't work. – Barry Jan 23 '19 at 15:48
  • Also an option, extend the libraryClass and add a field object_name_ and generic method like - `double HistogramStatistics() { return ComputeStatistics([object_name_](const auto& v) {return histogram(object_name_, v); }`. Or create a facade class like HistorgramStatisic which took object and it's name as a constructor parameter. – Victor Gubin Jan 23 '19 at 16:03

3 Answers3

3

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.

Barry
  • 286,269
  • 29
  • 621
  • 977
1

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.

Barry
  • 286,269
  • 29
  • 621
  • 977
  • well, if we allow using macros, we can as well do: `#define HISTOGRAM_LAMBDA(X) [] (const auto& v) { return histogram(X, v);};` the idea of the question is to acheive it without macros, in some "modern" `constexpr` way. But thanks for your contribution. – Przemysław Czechowski Jan 24 '19 at 15:40
  • @PrzemysławCzechowski Those... aren't equivalent. This is a general solution for encasing a value in a type, that is a very specific solution to this problem that just saves you a little bit of typing. – Barry Jan 24 '19 at 15:52
-1

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>());
max66
  • 65,235
  • 10
  • 71
  • 111