I have the following c++ code:
#include <array>
#include <iostream>
typedef unsigned char uchar;
class A {
public:
template <size_t N, uchar value>
static inline constexpr std::array<uchar, N> filledArray() {
std::array<uchar,N> ret{};
ret.fill(value);
return ret;
}
std::array<uchar, 5> upper = A::filledArray<5, 'A'>();
};
int main() {
A blah;
for (int i = 0; i < 5; ++i)
std::cout << blah.upper[i] << std::endl;
return 0;
}
g++ compiles it without warnings and the output is As, as expected.
but clang++-4.0 produces:
clang++-4.0 -std=c++14 main.cpp -o clangOut
main.cpp:9:47: warning: inline function 'A::filledArray<5, 'A'>' is not defined [-Wundefined-inline]
static inline constexpr std::array<uchar, N> filledArray() {
^
main.cpp:15:34: note: used here
std::array<uchar, 5> upper = A::filledArray<5, 'A'>();
^
1 warning generated.
/tmp/main-b6fac8.o: In function `A::A()':
main.cpp:(.text._ZN1AC2Ev[_ZN1AC2Ev]+0x15): undefined reference to `std::array<unsigned char, 5ul> A::filledArray<5ul, (unsigned char)65>()'
clang: error: linker command failed with exit code 1 (use -v to see invocation)
seems like clang is does not see, that i instantiate the filledArray function.
if I call filledArray with the proper template arguments in the main or any other function, the warning disappears and clangOut also prints As as expected.
- am I doing something stupid here?
- is the gcc version doing what i think (initialise upper with As at compile time)?
- is this a bug in clang?
- am I doing something stupid here?
Yes, the function filledArray()
always calls a non-constexpr std::array:fill
, so declaring it constexpr
is strictly speaking an error (according to [dcl.constexpr]/5 "the program is ill-formed, no diagnostic required").
- is the gcc version doing what i think (initialise upper with As at compile time)?
Many compilers relax the [dcl.constexpr]/5 requirement and silently ignore constexpr
when it is used in a non-constexpr context. But with optimization on they can also easily see through inline calls such as the construction of std::array
and std::array::fill()
and will most probably evaluate your function compile-time, even if it wasn't declared constexpr
(demo).
- is this a bug in clang?
Yes it's a clang bug (#18781).
Clang cannot compile static constexpr
class members.
It cannot properly "see" when such elements are ODR-used. To verify, you can just place A::filledArray<5, 'A'>();
by itself somewhere inside main()
, that will "fix" the compilation (but not the ill-formedness).
Another example:
#include <iostream>
struct foo
{
constexpr static const char* me = "foo";
};
int main ()
{
foo f;
std::cout << f.me << std::endl;
}
Changing f.me
to foo::me
also "fixes" it.
As a workaround you can change constexpr
to const
.
- Is it doing what i think? -> no (tested by setting a breakpoint)
the following does (inspired by the answer to Array Initialisation Compile Time - Constexpr Sequence)
#include <array>
#include <iostream>
#include <utility>
template <typename T, T value>
constexpr T generate_ith_number(const std::size_t) {
static_assert(std::is_integral<T>::value, "T must to be an integral type");
return value;
}
template <typename T, T value, T... Is>
constexpr auto make_sequence_impl(std::integer_sequence<T, Is...>)
{
return std::integer_sequence<T, generate_ith_number<T, value>(Is)...>{};
}
template <typename T, T value, std::size_t N>
constexpr auto make_sequence()
{
return make_sequence_impl<T, value>(std::make_integer_sequence<T, N>{});
}
template <typename T, T... Is>
constexpr auto make_array_from_sequence_impl(std::integer_sequence<T, Is...>)
{
return std::array<T, sizeof...(Is)>{Is...};
}
template <typename Seq>
constexpr auto make_array_from_sequence(Seq)
{
return make_array_from_sequence_impl(Seq{});
}
typedef unsigned char uchar;
class A {
public:
template <size_t N, uchar value>
static inline constexpr std::array<uchar, N> filledArray() {
return make_array_from_sequence(make_sequence<uchar, value, N>());
}
// long route
std::array<uchar, 5> upper = A::filledArray<5, 'A'>();
// taking a short cut
std::array<uchar, 45> blah = make_array_from_sequence_impl(make_sequence<uchar, 'A', 45>());
void dummy() {A::filledArray<5, 'A'>();} // make clang happy
};
int main() {
A blah;
for (int i = 0; i < 5; ++i)
std::cout << blah.upper[i] << std::endl;
for (int i = 0; i < 45; ++i)
std::cout << blah.blah[i] << std::endl;
return 0;
}
which actually also answers #1. yes, it is stupid trying to optimise code that will never be performance critical, fail at doing so, hit compiler bugs and waste many hours trying to find a solution that is too verbose and too hard to read for production. :D