5

I have the following function:

template <size_t TSize>
consteval size_t indexOf(SomeEnum someEnum,
                         const std::array<SomeEnum, TSize> &arr) {
  for (size_t i = 0; i < TSize; ++i) {
    if (arr[i] == someEnum) {
      return i;
    }
  }
  // How to fail here?
  return SOME_DEFAULT_WRONG_VALUE;
}

The function should fail instead of returning a default value, but I can't throw an exception or call assert. I can add a static_assert to every call to the function (with a macro it will be less horrible), but I'd prefer a solution that works in the function. Is there a way to trigger a compilation failure in such a scenario?

Barry
  • 286,269
  • 29
  • 621
  • 977
nihohit
  • 498
  • 3
  • 23
  • 2
    Generally these kinds of functions don't fail if they can't find the data, they return an out of bounds marker (usually -1). It's perfectly valid to search for something that doesn't exist in your collection. – Blindy Apr 29 '21 at 15:29
  • 2
    Why can't you throw an exception or use `assert`? – chris Apr 29 '21 at 15:30
  • @chris `consteval`. – Tanveer Badar Apr 29 '21 at 15:31
  • @chris: You can't use `assert` in a constant expression context. – Nicol Bolas Apr 29 '21 at 15:31
  • 1
    You can still `throw` from a `constexpr`. I'm not familiar enough with `consteval` to be certain, but I believe you should be able to `throw` from them as well. – François Andrieux Apr 29 '21 at 15:32
  • 1
    Can you please clarify whether you *think* throwing is not allowed in `consteval` or if you have an external requirement that you not use `throw`? – François Andrieux Apr 29 '21 at 15:38
  • @FrançoisAndrieux: You can *write* `throw` in a `constexpr` function. You cannot *execute* any `throw` statements in constant evaluation; that causes a compile error. `consteval` only forces any calls to the function to be constant expressions; it doesn't change how they operate. So the standard rules of `constexpr` apply. – Nicol Bolas Apr 29 '21 at 15:40
  • @NicolBolas, Then why can't throwing be used in `consteval` to produce a hard error when that code path is executed? – chris Apr 29 '21 at 15:44
  • 1
    @chris: It can. One of the answers mentions this. – Nicol Bolas Apr 29 '21 at 15:46
  • @NicolBolas, Then what was wrong with my original comment? – chris Apr 29 '21 at 15:46
  • @chris: I was only speaking of `assert`. – Nicol Bolas Apr 29 '21 at 15:47
  • @NicolBolas But [assert](https://godbolt.org/z/M13s1P5o7) seems to work as well. If the element exists, it compiles fine. – cigien Apr 29 '21 at 15:51
  • @NicolBolas, (Sorry, I'm multitasking right now and slow to reply. I also probably confused your initial reply with the more general one following it.) According to [cppreference](https://en.cppreference.com/w/cpp/error/assert), `assert` should work as intended, even in an executed code path, as of C++17. I actually thought it was since C++14, but I guess either works in a C++20 context. Standardwise, that blurb is in [\[assertions.assert\]](https://timsong-cpp.github.io/cppwp/n4861/assertions.assert). – chris Apr 29 '21 at 15:57
  • "If I add an exception it is thrown at runtime, though." Your function is `consteval`, it is not thrown at runtime. – Barry Apr 29 '21 at 22:12
  • Also please don't edit answers into questions. – Barry Apr 29 '21 at 22:12
  • @Barry - "Your function is consteval, it is not thrown at runtime." if you want, I can record a screen capture and show you that you're wrong. I assume that it means that the Apple Clang compiler isn't entirely compliant with the standard. – nihohit Apr 30 '21 at 11:31
  • @Barry, I don't understand why you edited my additional information out of the answer - it contained info which might be relevant to future viewers, who will want to understand or confirm this erroneous behavior. – nihohit Apr 30 '21 at 11:32

3 Answers3

15

Is there a way to trigger a compilation failure in such a scenario?

If the goal is to trigger a compilation failure, then the easiest thing to do is to throw an exception. Doesn't matter what the exception is, since it won't actually be thrown, it's the act of throwing an exception that will trigger a compile error because throwing is not allowed at constant evaluation time:

template <size_t TSize>
consteval size_t indexOf(SomeEnum someEnum,
                         const std::array<SomeEnum, TSize> &arr) {
  for (size_t i = 0; i < TSize; ++i) {
    if (arr[i] == someEnum) {
      return i;
    }
  }

  throw "failed to find someEnum";
}

If you want to be more explicit, you can just have a non-constexpr function without a definition:

void trigger_consteval_failure(char const*);

template <size_t TSize>
consteval size_t indexOf(SomeEnum someEnum,
                         const std::array<SomeEnum, TSize> &arr) {
  for (size_t i = 0; i < TSize; ++i) {
    if (arr[i] == someEnum) {
      return i;
    }
  }

  trigger_consteval_failure("failed to find someEnum");
}

In both cases, if you're looking for a value that is in the array, invoking this function is a valid constant expression. But if the index is not found, then we end up doing something that's now allowed in constant expressions and that's a hard compile error regardless, as desired.

It'd be nice if we could produce a better stack trace in this case, but I don't think there's actually a way to do that.

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

You might simply omit the return

template <size_t TSize>
consteval size_t indexOf(SomeEnum someEnum,
                         const std::array<SomeEnum, TSize> &arr) {
  for (size_t i = 0; i < TSize; ++i) {
    if (arr[i] == someEnum) {
      return i;
    }
  }
}

Demo (clang warns though about that method).

Compiler would reject code reaching that path.

Demo

throw exception seems cleaner:

template <size_t TSize>
consteval size_t indexOf(SomeEnum someEnum,
                         const std::array<SomeEnum, TSize> &arr) {
  for (size_t i = 0; i < TSize; ++i) {
    if (arr[i] == someEnum) {
      return i;
    }
  }
  throw 42; // or more meaningful exception
}

Demo

Jarod42
  • 203,559
  • 14
  • 181
  • 302
3

You should avoid working with indices and instead use std::find which is constexpr now. If you want an index, you can just use pointer arithmetic subtraction from the begining of the array to compute the index.

However, if you can't do that, then just return TSize; it should act like the end iterator.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • 1
    Its (almost) always best to answer the question as code attached in questions is usually just an example of the problem and a way to communicate the solution. –  Feb 24 '22 at 12:20