Clang warning about static templated constexpr (in

2019-02-25 08:09发布

问题:

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.

  1. am I doing something stupid here?
  2. is the gcc version doing what i think (initialise upper with As at compile time)?
  3. is this a bug in clang?

回答1:

  1. 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").

  1. 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).

  1. 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.



回答2:

  1. 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



标签: c++ gcc clang