Is It a Conforming Compiler Extension to Treat Non-Constexpr Standard Library Functions as Constexpr

constexpr, or not constexpr, that is the question

A function that's constexpr means that the function potentially can be evaluated at compile-time. The function must however also be callable with a run-time value, and produce a run-time result.

A variable that's constexpr has to be a compile time constant. Period.

What that means for your validateIP function is that you don't need to make count constexpr. You can write a function and mark it as constexpr.

When you call the function with a compile time constant, it will get evaluated at compile time.

If you call it with a run time value it will get evaluated at run time.

#include <string_view>
#include <iostream>

constexpr bool validateIP(const std::string_view& ip) {
int count = 0;
for (auto& c : ip) {
if (c == '.') {
++count;
}
}

return count == 3;
}

int main()
{
// Assigning the value to a constexpr is not needed to make the function
// be evaluated at compile time, but it proves that it is in this case
constexpr auto isValid1 = validateIP("123.456.789.0");

std::cout << isValid1;
}

std::abs can be used in constexpr function, but only if it's templated. Why?

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.

Constexpr compile error using std::acos with clang++ not g++

Clang is correct here, we are not allowed to use acos in a constant expression.

The issue is that acos is not marked constexpr in the standard but gcc treats some functions not marked in the standard including acos as constexpr. This is a non-conforming extension and should eventually be fixed in gcc.

Builtin functions are often used to constant fold and we can see if we use -fno-builtin with gcc it disables this non-conforming behavior and we will receive the following error:

error: call to non-constexpr function 'double acos(double)'
constexpr T pi{std::acos(T(-1.0))};
^

Why doesn't the C++ standard library provide constexpr versions of the cmath functions?

It hasn't been standardized yet. An initial proposal was submitted just last week, but only covering utility and linear operations and not any transcendental functions. Math is hard and floating-point math is complicated. For example, implementations don't allow overflows to infinity in constexpr, but this isn't yet clearly standardized.

The compiler's constexpr interpreter would have to special-case the math library interface, since unlike the rest of the standard library, it can't see its implementation.

GCC does offer constant evaluation of math functions as a nonconforming extension.

Work around disregarded constexpr in Visual Studio?

It looks like your project includes the standard std::log2 function which the compiler confuses with the your log2 function. This can happen even if you don't #include <cmath> because standard headers are allowed to include any other standard headers. This is also another example of using namespace std; backfiring.

One solution is to rename your constexpr function to something else :

#include <bitset>
#include <iostream>

using namespace std;

constexpr int logTwo(const unsigned int x) {
return x < 4 ? 1 : 1 + logTwo(x / 2);
}

int main() {
bitset<logTwo(2)> foo;
int bar[logTwo(8)];

cout << logTwo(8) << endl;
}

Demo

Edit : It seems that using namespace std; may be unrelated in this case. The standard log2 function may be available at the global namespace anyway.

Getting constexpr to work with pow in C++17 on OSX

First, you can't initialize constexpr class members with functions that aren't constexpr and std::pow isn't constepxr in standard C++17 . The workaround is to declare them const. While they can't be used in places that require a compile time const they are immutable. A traditional approach is declaring them in header which you include as needed in source files. Then you hneed one implementation file that defines the static const members.

If your code requires compile time const's or constexpr your only option is to write your own pow.

Here's one way to initialize const statics with functions that are not constexpr prior to executing the main() using a portion of your question that shows the technique:

Create a header, constinit.h, that declares the class

// include header guards
// declare the static consts
struct ClassName {
static double DEFAULT_TARGET_TINITIAL_DIGITS_FROM_1; // represents 0.99
static double DEFAULT_TARGET_INITIAL_PBAD; // to be initialized by pow
};

Create an implementation file that initializes the statics:

#include "constinit.h"
#include <cmath>

double ClassName::DEFAULT_TARGET_TINITIAL_DIGITS_FROM_1{ 2 }; // represents 0.99
double ClassName::DEFAULT_TARGET_INITIAL_PBAD = (1 - std::pow(10, -DEFAULT_TARGET_TINITIAL_DIGITS_FROM_1));

To use the statics:

#include <iostream>
#include "constinit.h"


int main()
{
std::cout << ClassName::DEFAULT_TARGET_INITIAL_PBAD << std::endl;
}

If constexpr for compile time initialization is required you need to define your own constexpr pow function. This works in C++17:

    #pragma once // or header guards per your preference

constexpr double my_pow(double x, int exp)
{
int sign = 1;
if (exp < 0)
{
sign = -1;
exp = -exp;
}
if (exp == 0)
return x < 0 ? -1.0 : 1.0;
double ret = x;
while (--exp)
ret *= x;
return sign > 0 ? ret : 1.0/ret;
}
class ClassName {
public:
static constexpr double DEFAULT_TARGET_TINITIAL_DIGITS_FROM_1 = 2; // represents 0.99
static constexpr double DEFAULT_TARGET_TFINAL_DIGITS_FROM_0 = 10; // represents 1e-10
static constexpr double DEFAULT_TARGET_INITIAL_PBAD = (1 - my_pow(10, -DEFAULT_TARGET_TINITIAL_DIGITS_FROM_1));
static constexpr double DEFAULT_TARGET_FINAL_PBAD = my_pow(10, -DEFAULT_TARGET_TFINAL_DIGITS_FROM_0);
static constexpr double DEFAULT_ERROR_TOL_DIGITS = 0.9; // as a fraction of digits in the last place from the above.
static constexpr double DEFAULT_SAMPLE_TIME = 1;

// more unrelated code
};


Related Topics



Leave a reply



Submit