2

Supposedly std::abs is not constexpr in the standard (even in C++20). But in practice I found out that I can compile it as constexpr under the very peculiar condition that the function is templated. See this completely working example:

template<class T>
constexpr T f(const T input) {
   return std::abs(input);
}

int main() {
   int i = -1;
   int a = f(i);
   return 0;
}

The code:

  • Compiles fine with GCC, with and without the template.
  • It doesn't work in Clang.
  • And in Visual Studio it compiles with the template line, but fails compilation without the template.
Amir Kirsh
  • 12,564
  • 41
  • 74
Basti
  • 2,228
  • 1
  • 19
  • 25
  • No it actually works! Even when invoking. I've used this "trick" without knowing for some time. – Basti Oct 06 '20 at 06:19
  • 2
    [Clang](https://wandbox.org/permlink/nvONlftZIhPngkVN) complains for both cases. – songyuanyao Oct 06 '20 at 06:20
  • Does this answer your question? [Why isn't abs constexpr?](https://stackoverflow.com/questions/27708629/why-isnt-abs-constexpr) – Sander De Dycker Oct 06 '20 at 06:20
  • For more details on why gcc allows this : [Is it a conforming compiler extension to treat non-constexpr standard library functions as constexpr?](https://stackoverflow.com/questions/27744079/is-it-a-conforming-compiler-extension-to-treat-non-constexpr-standard-library-fu) – Sander De Dycker Oct 06 '20 at 06:21
  • 1
    Try in MSVC to add `constexpr` to your `i` and `a` - does it compile? If not then `f` is not really a `constexpr` function. – Amir Kirsh Oct 06 '20 at 06:35
  • I mean it's still constexpr if it compiles like that, just not consteval. But it probably shouldn't. – Basti Oct 06 '20 at 06:39
  • If it’s not specified such way, then it’s probably not portable. The idea of the function itself seems quite simple, so some compiler might just made them `constexpr`. However you can’t rely on that – Ranoiaetep Oct 06 '20 at 06:45
  • 1
    *"I mean it's still constexpr if it compiles like that"* - No, it doesn't mean that. A template declared `constexpr` "falls back" to being non-constexpr if an instantiation is not-constexpr – StoryTeller - Unslander Monica Oct 06 '20 at 07:41
  • Related: [Call non constexpr from constexpr template function](https://stackoverflow.com/questions/48541044/call-non-constexpr-from-constexpr-template-function) – Amir Kirsh Oct 06 '20 at 10:08

1 Answers1

2

For a regular function the compiler may know, based on the type of the function parameters, if the inner code can be potentially evaluated in compile time. This is why you get an error for calling std::abs in MSVC and clang. The behavior of gcc is based on its decision to implement std::abs as constexpr which is by the way a questionable decision.

For a template function the compiler cannot know if the inner code can be evaluated in compile time, as it may be based on the actual type of the template arguments, with different functions overload being called. While most compilers would decide not to check whether all possible overloads of std::abs cannot be constexpr, thus letting the code pass compilation, theoretically a compiler may check (in very specific cases that can be checked, like this one) and since the user is not allowed to extend std by adding a new version of abs (the list of allowed extensions to std is closed by the spec) it is possible to see that the function can never be constexpr and thus to generate a compilation error. In the more general case however, the compiler cannot check for a template function if all possible cases cannot produce a constexpr function, since it sees only the available overloads for the inner call, per each call to the template function, and there might be other available overloads for the inner call, when the template is called elsewhere.


Note that making a constexpr function a template, just so it can get compiled, would not be a good approach. The actual decision if the function is constexpr (i.e. can be called in compile time) would be based on the actual call, and if in all cases the function cannot be constexpr you are trying in a way to cheat the compiler but eventually are cheating mainly yourself...


By the way, in my check with clang 10.1 and trunk versions, I don't get compilation error on the template version, this code compiles both with gcc and clang:

template<typename T>
constexpr T myabs(T t) {
    return std::abs(t);
}

int main() {
    int i = myabs(3);
}

While this compiles with gcc (which implements std::abs as constexpr) and fails with clang:

int main() {
    constexpr int i = myabs(3);
}

It seems that both gcc and clang do not generate an error even if the inner call inside a constexpr template function is not dependent on the template parameters and can never be a constant expression:

int myabs() {
    return 42;
}

template<class T>
constexpr int f() {
    // this is never a contexpr
    // yet gcc and clang are ok with it
    return myabs();
}

And again, this is allowed as no diagnostic is required for non-conforming constexpr template functions:

[dcl.constexpr] 9.2.5/7 - The constexpr and consteval specifiers:

[...] If no specialization of the template would satisfy the requirements for a constexpr function when considered as a non-template function, the template is ill-formed, no diagnostic required.

Amir Kirsh
  • 12,564
  • 41
  • 74
  • 1
    In the second case, the function template is ill-formed NDR. No set of template arguments will produce an actual constexpr function (until `std::abs` is made constexpr formally). – StoryTeller - Unslander Monica Oct 06 '20 at 07:42
  • I always thought successful compilation as constexpr means it can at least in theory be run at compile time? Is that assumption wrong? – Basti Oct 06 '20 at 07:59
  • @Basti not for a template function, the compiler is not obliged to check all possible overloads and see that it can never be a constexpr, in most cases it cannot actually do that check as it sees only the available overloads per each call to the template function and there might be other available overloads for the inner call, when the template is called elsewhere. There are few cases that it can be checked, like for example when calling directly an std:: function, but then again the compiler is not obliged to check. – Amir Kirsh Oct 06 '20 at 08:04
  • https://timsong-cpp.github.io/cppwp/n4861/dcl.constexpr#7.sentence-2 – StoryTeller - Unslander Monica Oct 06 '20 at 09:44
  • @StoryTeller-UnslanderMonica would add it to the answer, thanks. – Amir Kirsh Oct 06 '20 at 09:45