constexpr differences between GCC and clang - c++

The following compiles in GCC 9 but not in clang 10 and I'm wondering which of the two compilers is standard conforming:
template<typename T>
struct A {
static const T s;
static const T v;
};
template<typename T>
constexpr const T A<T>::s = T(1);
template<typename T>
constexpr const T A<T>::v = A<T>::s;
int main(int, char**) {
constexpr auto a = A<double>::v;
return 0;
}
This is intended to be a minimal example of a bigger issue which is why the fields s and v are explicitly declared as const but are defined as constexpr, this is intentional.
Is GCC correct to compile that code or is clang correct to reject it?

Compilers are only required to treat static const variables of integral and enum types as constexpr if they are initialize with a constant expression. This made it possible to use them as array lengths before constexpr was added to the language.

Related

C++ code that compiles in gcc 9.3 but not in gcc 10.2

The following code compiles in gcc 9.3, but not in gcc 10.2:
constexpr std::array<int, 2> opt = {1,2};
template <typename T>
constexpr auto f(const T& arr)
{
std::array<int, arr.size()> res{};
return res;
}
int main()
{
auto res = f(opt);
}
The code is in https://godbolt.org/z/8hb6M8.
The error given by gcc10.2 is that arr.size() is not a constant expression.
Which compiler is right? 9.3 or 10.2?
If 10.2 is right, how can I define a compile time array and pass its size (and the array) as an argument?
Not sure which one is right but for
how can I define a compile time array and pass its size (and the array) as an argument?
You can change the function to
template <typename T, std::size_t N>
constexpr auto f(const std::array<T, N>& arr)
{
std::array<int, N> res{};
return res;
}
and now the size gets uplifted into the template parameter.
Another alternative which works in both compilers and doesn't require changing the template declaration:
std::array<int, std::tuple_size_v<T>> res{};
The definition of constant expressions has been changed since C++20, you can find several changes on this.
The shorter answer is to change your function signature:
template <typename T>
constexpr auto f(const T& arr);
into:
template <typename T>
constexpr auto f(const T arr);
Then it works.

msvc and gcc different behavior

#include <iostream>
template<typename T>
const T unit{ 1 };
template<typename T>
struct data
{
typename T::value_type val;
};
struct decimal_def
{
using value_type = int;
static const value_type unit_val() {
return 10;
}
};
template<typename T>
const data<T> unit<data<T>> { T::unit_val() };
auto decimal_data_unit = unit<data<decimal_def>>;
int main(){
std::cout << decimal_data_unit.val << std::endl;
return 0;
}
exoect: 10
gcc: 10
msvc: 0
I think the reason of the difference between gcc and msvc is that compilers optimize unit_val() differently.
Dynamic initialization of templated non-local variables is always unordered, even relative to other variables defined in the same translation unit (in part because templated variables might also be defined in other translation units). In this simple case, you can avoid the issue by using constant initialization instead: add constexpr to decimal_def::unit_val to enable that, and to the variable template definitions for clarity.

Constexpr member function

Suppose I have a struct template S that is parametrized by an engine:
template<class Engine> struct S;
I have two engines: a "static" one with a constexpr member function size(), and a "dynamic" one with a non-constexpr member function size():
struct Static_engine {
static constexpr std::size_t size() {
return 11;
}
};
struct Dynamic_engine {
std::size_t size() const {
return size_;
}
std::size_t size_ = 22;
};
I want to define size() member function in S that can be used as a constexpr if the engine's size() is constexpr. I write:
template<class Engine>
struct S {
constexpr std::size_t size() const {
return engine_.size();
}
Engine engine_;
};
Then the following code compiles with GCC, Clang, MSVC and ICC:
S<Static_engine> sta; // not constexpr
S<Dynamic_engine> dyn;
constexpr auto size_sta = sta.size();
const auto size_dyn = dyn.size();
Taking into account intricacies of constexpr and various "ill-formed, no diagnostic is required", I still have the question: is this code well-formed?
Full code on Godbolt.org
(I tagged this question with both c++17 and c++20 in case this code has different validity in these two standards.)
The code is fine as written.
[dcl.constexpr]
6 If the instantiated template specialization of a constexpr
function template or member function of a class template would fail to
satisfy the requirements for a constexpr function or constexpr
constructor, that specialization is still a constexpr function or
constexpr constructor, even though a call to such a function cannot
appear in a constant expression. If no specialization of the template
would satisfy the requirements for a constexpr function or constexpr
constructor when considered as a non-template function or constructor,
the template is ill-formed, no diagnostic required.
The member may not appear in a constant expression for the specialization that uses Dynamic_engine, but as the paragraph above details, that does not make S::size ill-formed. We are also far from ill-formed NDR territory, since valid instantations are possible. Static_engine being a prime example.
The quote is from n4659, the last C++17 standard draft, and similar wording appears in the latest C++20 draft.
As for the evaluation of sta.size() as a constant expression, going over the list at [expr.const] I cannot find anything that is disallowed in the evaluation itself. It is therefore a valid constant expression (because the list tells us what isn't valid). And in general for a constexpr function to be valid, there just needs to exist some set of arguments for which the evaluation produces a valid constant expression. As the following example form the standard illustrates:
constexpr int f(bool b)
{ return b ? throw 0 : 0; } // OK
constexpr int f() { return f(true); } // ill-formed, no diagnostic required
struct B {
constexpr B(int x) : i(0) { } // x is unused
int i;
};
int global;
struct D : B {
constexpr D() : B(global) { } // ill-formed, no diagnostic required
// lvalue-to-rvalue conversion on non-constant global
};
Yes.
Functions may be marked as constexpr without being forced to be evaluated at compile-time. So long as you satisfy the other requirements for marking a function as constexpr, things are okay (returns a literal type, parameters are literals, no inline asm, etc.). The only time you may run into issues is if it's not actually possible to create arguments that satisfy the function being called as a core constant expression. (e.g., if your function had undefined behavior for all values, then your function would be ill-formed NDR)
In C++20, we received the consteval specifier that forces all calls to the function to be able to produce a compile-time constant (constexpr).
Not a direct answer but an alternative way:
struct Dynamic_Engine
{
using size_type = size_t;
size_type size() const
{
return _size;
}
size_type _size = 22;
};
struct Static_Engine
{
using size_type = std::integral_constant<size_t, 11>;
size_type size() const
{
return size_type();
}
};
template <typename ENGINE>
struct S
{
auto size() const
{
return _engine.size();
}
ENGINE _engine;
};
int main()
{
S<Static_Engine> sta;
S<Dynamic_Engine> dyn;
const auto size_sta = sta.size();
const auto size_dyn = dyn.size();
static_assert(size_sta == 11);
}
I had the same kind of problems and IMHO the easiest and more versatile solution is to use std::integral_constant. Not more needs to juggle with constexpr as the size information is directly encoded into the type
If you still really want to use constexpr (with its extra complications) you can do:
struct Dynamic_Engine
{
size_t size() const
{
return _size;
}
size_t _size = 22;
};
struct Static_Engine
{
static constexpr size_t size() // note: static
{
return 11;
}
};
template <typename ENGINE>
struct S
{
constexpr size_t size() const
{
return _engine.size();
}
ENGINE _engine;
};
int main()
{
S<Static_Engine> sta;
S<Dynamic_Engine> dyn;
constexpr size_t size_sta = sta.size();
const size_t size_dyn = dyn.size();
static_assert(size_sta == 11);
}

constexpr expression and variable lifetime, an example where g++ and clang disagree

Consider the simple C++11 code:
template<int N>
struct Foo {};
template <int N>
constexpr int size(const Foo<N>&) { return N; }
template <int N>
void use_size(const Foo<N>& foo) { constexpr int n = size(foo); }
int main()
{
Foo<5> foo;
constexpr int x = size(foo); // works with gcc and clang
// _but_
use_size(foo); // the same statement in the use_size()
// function _only_ works for gcc
}
I can successfuly compile it with g++ -std=c++11 foo.cpp
however if I use clang++, clang++ -std=c++11 foo.cpp I get
foo.cpp:15:28: error: constexpr variable 'n' must be initialized by a constant expression
void use_size(const Foo<N>& foo) { constexpr int n = size(foo); }
~~~~~^~~~
foo.cpp:23:5: note: in instantiation of function template specialization 'use_size<5>' requested here
use_size(foo); // the same statement in the use_size()
^
1 error generated.
(nb: compiler versions. I have checked the previous statement with g++ version 5.3.1 and 7.2.1 and with clang++ version 3.6.2 and 5.0.0)
My question: which of g++ or clang is right? What is the problem?
My interpretation is that clang++ is right and g++ is too permissive.
We can find a close example ([expr.const] section, page 126) in the standard https://isocpp.org/std/the-standard (draft can be downloaded, attention big PDF! ).
constexpr int g(int k) {
constexpr int x = incr(k);
return x;
}
where it is explained that:
error: incr(k) is not a core constant expression because
lifetime of k began outside the expression incr(k)
This is exactly what is happening in the use_size() function with the foo argument, even if the size() function only use the N template parameter.
template <int N>
constexpr int size(const Foo<N>&) { return N; }
template <int N>
void use_size(const Foo<N>& foo) { constexpr int n = size(foo); }
I was expecting Clang to be wrong in this case. It should evaluate your function call as being a constant expression, simply because you use only the template parameter, and not the object itself. Since you don't use the object in your constexpr function, there should be nothing prohibit compile time evaluation.
However, there's a rule in the standard that says object that began their lifetime preceding the constant expression such as a reference is not useable as constexpr.
There is a simple fix in that case. I think it didn't like the reference:
template <int N> // pass by value, clang is happy
void use_size(Foo<N> foo) { constexpr int n = size(foo); }
Here's a live example
Alternatively, you can also copy your foo object and use that local object:
template <int N>
void use_size(const Foo<N>& foo) {
auto f = foo;
constexpr int n = size(f);
}
Live example

using result of constexpr function as a template parameter (clang vs gcc)

Please take a look at the code below, sorry that is a bit lengthy, but I did my best to reproduce the problem with a minimum example (there is also a live copy of it). There I basically have a metafunction which returns the size of string literal, and constexpr function which wraps it. Then when I call those functions in a template parameter gcc (5.4, 6.2) is happy with it, but clang (3.8, 3.9) barfs with "non-type template argument is not a constant expression" in test body on strsize(s). If I replace with a str_size<S> both compilers are happy. So the questions are:
whether that is a problem with clang, or my code?
What is the way to make it compile on both clang and gcc with constexpr function?
template<size_t N> using string_literal_t = char[N];
template<class T> struct StrSize; ///< metafunction to get the size of string literal alikes
/// specialize StrSize for string literals
template<size_t N>
struct StrSize <string_literal_t<N>>{ static constexpr size_t value = N-1; };
/// template variable, just for convenience
template <class T>
constexpr size_t str_size = StrSize<T>::value;
/// now do the same but with constexpr function
template<class T>
constexpr auto strsize(const T&) noexcept-> decltype(str_size<T>) {
return str_size<T>;
}
template<class S, size_t... Is>
constexpr auto test_helper(const S& s, index_sequence<Is...>) noexcept-> array<char, str_size<S>> {
return {s[Is]...};
}
template<class S>
constexpr auto test(const S& s) noexcept-> decltype(auto) {
// return test_helper(s, make_index_sequence<str_size<S>>{}); // this work in both clang and gcc
return test_helper(s, make_index_sequence<strsize(s)>{}); // this works only in gcc
}
auto main(int argc, char *argv[])-> int {
static_assert(strsize("qwe") == 3, "");
static_assert(noexcept(test("qwe")) == true, "");
return 0;
}
Clang is correct here. The problem is in the code and in GCC, which erroneously accepted it. This was fixed in GCC 10: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=66477
According to the standard expr.const#5.12:
An expression E is a core constant expression unless the evaluation of E, following the rules of the abstract machine, would evaluate one of the following:
...
an id-expression that refers to a variable or data member of reference type unless the reference has a preceding initialization and either
it is usable in constant expressions or
its lifetime began within the evaluation of E;
And here the compiler is unable to verify the validity of reference in test(const S& s).
Actually there is a nice article to read here: https://brevzin.github.io/c++/2020/02/05/constexpr-array-size/
As to your other question:
What is the way to make it compile on both clang and gcc with constexpr function?
You can replace references with std::array passed by value:
#include <array>
using namespace std;
template<class T> struct StrSize;
template<size_t N>
struct StrSize <array<char,N>>{ static constexpr size_t value = N-1; };
template <class T>
constexpr size_t str_size = StrSize<T>::value;
template<class T>
constexpr auto strsize(const T&) noexcept-> decltype(str_size<T>) {
return str_size<T>;
}
template<class S, size_t... Is>
constexpr auto test_helper(const S& s, index_sequence<Is...>) noexcept-> array<char, str_size<S>> {
return {s[Is]...};
}
constexpr auto test(array<char,4> s) noexcept-> decltype(auto) {
return test_helper(s, make_index_sequence<strsize(s)>{});
}
int main() {
static_assert(noexcept(test({"qwe"})) == true, "");
}
Demo: https://gcc.godbolt.org/z/G8zof38b1