1

I'm trying to find a way to check whether a lambda can be constant evaluated or not in C++17. Let's assume the lambda I want to check takes no parameters.

I stumbled upon this question, whose accepted answer looks something like this:

template <class Lambda, int = (Lambda{}(), 0)>
constexpr std::true_type is_cx_invocable(Lambda) {
    return {};
}

constexpr std::false_type is_cx_invocable(...) {
    return {};
}

Which has the following issues:

  1. It only works in C++20 (as requires captureless lambdas to be default constructible) and I'm looking for a C++17 solution;

  2. If is_cx_invocable returns false, it's not clear whether the problem is Lambda not being default constructible or its call operator not being constexpr, e.g.:

int main() {
    static_assert(is_cx_invocable([] { return 0; }));

    int i{};
    auto cxLambda = [i] { return 0; };
    (std::array<int, cxLambda()>{}, (void)0);
    //               ^ cxLambda is indeed constexpr invocable
    //                 (but not default constructible)

    auto isCxLambdaCx = is_cx_invocable(cxLambda);
    static_assert(!isCxLambdaCx);  // This fails
}

So, I tried this instead:

template <class Lambda>
constexpr auto is_cx_invocable(Lambda lambda)
    -> std::integral_constant<bool, (lambda(), 1)> { // or
    //-> std::enable_if_t<(lambda(), 1), std::true_type> {
    return {};
}

constexpr std::false_type is_cx_invocable(...) {
    return {};
}

Which:

  1. Doesn't require C++20 features;

  2. Fixes issue (2) above:

int main() {
    int i{};
    auto cxLambda = [i] { return 0; };
    auto isCxLambdaCx = is_cx_invocable(cxLambda);
    static_assert(isCxLambdaCx); // OK now
}

The problem is that this solution compiles fine with Clang and MSVC, while failing to compile on GCC. Here you are a Compiler explorer link to play with.

My questions are:

  1. Is my solution ill-formed (so GCC is right in rejecting my code)?

  2. Is there a portable (i.e. implementable with ISO C++ and independent on the compiler quirks) to implement the is_cx_invocable check in C++17?

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
paolo
  • 2,345
  • 1
  • 3
  • 17
  • 1
    GCC is probably correct as the return type of `is_cx_invocable` can hardly depent on the **value** of its argument, even when the compution involving the value is discarded. – chrysante Feb 28 '23 at 17:42
  • 1
    fwiw `auto` template arguments are C++17. Your first code reminds me of what was necessary before `template ` was possible – 463035818_is_not_an_ai Feb 28 '23 at 17:45
  • you didn't wrote negative test case. This one fails on all compilers. https://godbolt.org/z/sEna8Kz7j – Marek R Feb 28 '23 at 17:48
  • The constructibility issue is the reason `std::declval` exists at least. – chris Feb 28 '23 at 17:54
  • "*cxLambda is indeed constexpr invocable*" But it isn't. Not in any *meaningful* sense. If `cxLambda` is not able to be constructed at compile time, it *does not matter* if a member function happens to be `constexpr` or not; you can't call it at compile time since you cannot create an *object* at compile time. – Nicol Bolas Feb 28 '23 at 17:58
  • @NicolBolas I could write a function `template auto get_storage(T getSize)` that returns `std::array`, if `getSize()` can be constant evaluated, or `std::vector`, if it cannot; regardless of whether `getSize` itself is constructible at compile time. – paolo Mar 02 '23 at 15:47
  • @chris I know about `std::declval` but I'm not sure how to take advantage of it in this case. – paolo Mar 02 '23 at 15:50
  • @paolo, It was a direct replacement for `Lambda{}()`, but in the context of the full question, would be a different approach than taking an object as a parameter. I'm not sure if it changes the disagreement between the compilers. – chris Mar 03 '23 at 14:29
  • @chris I don't think I can use `std::declval` because (with my approach) I need to evaluate `Lambda{}()` to check if can be constant evaluated. `std::declval` only works in unevaluated contexts. – paolo Mar 06 '23 at 09:06
  • @paolo, Fair point, I never really processed that. You'd have to reach for one of the type-based SFINAE approaches, like `auto is_cx_invocable(const Lambda&) -> decltype(declval()(), true_type{})`, but the advantage it offers is not needing an object for the check and that doesn't seem important here. – chris Mar 06 '23 at 15:44

1 Answers1

0

There is a rule that function parameters shall not be potentially evaluated by default arguments of the same function: [dcl.fct.default]/9.

However, it seems that GCC is too strict. It appears to be enforcing a rule that a function parameter shall not be potentially evaluated until the start of the body (i.e. you can't do it in the trailing return type).

So I think Clang and MSVC are correct here. There is, in fact, no rule in the standard that makes this example ill-formed.

(I am also withdrawing the second half of my original answer since it seems it was due to a misunderstanding on my part. The metaprogramming technique you describe should work.)

Brian Bi
  • 111,498
  • 10
  • 176
  • 312