Filling a std::array at compile time and possible undefined behaviour with const_cast

笑着哭i 提交于 2019-12-04 07:47:54

As far as I can tell this is not undefined behavior. The proposal that added constexpr to operator[] happened before the changes that removed the implicit const from constexpr member functions. So it looks like they just added on constexpr without reflecting on the need for keeping const or not.

We can see form an earlier version of Relaxing constraints on constexpr functions that it says the following about mutating literals within a constant expression:

Objects created within a constant expression can be modified within the evalution of that constant expression (including the evaluation of any constexpr function calls it makes), until the evaluation of that constant expression ends, or the lifetime of the object ends, whichever happens sooner. They cannot be modified by later constant expression evaluations. [...]

This approach allows arbitrary variable mutations within an evaluation, while still preserving the essential property that constant expression evaluation is independent of the mutable global state of the program. Thus a constant expression evaluates to the same value no matter when it is evaluated, excepting when the value is unspecified (for instance, floating-point calculations can give different results and, with these changes, differing orders of evaluation can also give different results).

and we can see the earlier proposal I referenced points out the const_cast hack and it says:

In C++11, constexpr member functions are implicitly const. This creates problems for literal class types which desire to be usable both within constant expressions and outside them:

[...]

Several alternatives have been suggested to resolve this problem:

  • Accept the status quo, and require users to work around this minor embarrassment with const_cast.

No UB here, your member arr is non constant, you can "play" with its constness at will (well sort of, you get what I mean)

If your member was a constant expression then you'd have UB, because you have already initialized in the initalizer list and post creation you are not allowed to assume it has mutable value. Do whatever metaprogramming mumbo jumbo you wish inside the initializer list.

Not a direct answer to the question but hopefully something useful.

Having been troubled by std::array for a while I decided to see if it can be done better using user-code only.

Turns out it can:

#include <iostream>
#include <utility>
#include <cassert>
#include <string>
#include <vector>
#include <iomanip>

// a fully constexpr version of array that allows incomplete
// construction
template<size_t N, class T>
struct better_array
{
    // public constructor defers to internal one for
    // conditional handling of missing arguments
    constexpr better_array(std::initializer_list<T> list)
    : better_array(list, std::make_index_sequence<N>())
    {

    }

    constexpr T& operator[](size_t i) noexcept {
        assert(i < N);
        return _data[i];
    }

    constexpr const T& operator[](size_t i) const noexcept {
        assert(i < N);
        return _data[i];
    }

    constexpr T* begin() {
        return std::addressof(_data[0]);
    }

    constexpr const T* begin() const {
        return std::addressof(_data[0]);
    }

    constexpr T* end() {
        // todo: maybe use std::addressof and disable compiler warnings
        // about array bounds that result
        return &_data[N];
    }

    constexpr const T* end() const {
        return &_data[N];
    }

    constexpr size_t size() const {
        return N;
    }

private:

    T _data[N];

private:

    // construct each element from the initialiser list if present
    // if not, default-construct
    template<size_t...Is>
    constexpr better_array(std::initializer_list<T> list, std::integer_sequence<size_t, Is...>)
    : _data {
        (
         Is >= list.size()
         ?
         T()
         :
         std::move(*(std::next(list.begin(), Is)))
         )...
    }
    {

    }
};

// compute a simple factorial as a constexpr
constexpr long factorial(long x)
{
    if (x <= 0) return 0;

    long result = 1;
    for (long i = 2 ; i <= x ; result *= i)
        ++i;
    return result;
}

// compute an array of factorials - deliberately mutating a default-
// constructed array
template<size_t N>
constexpr better_array<N, long> factorials()
{
    better_array<N, long> result({});
    for (long i = 0 ; i < N ; ++i)
    {
        result[i] = factorial(i);
    }
    return result;
}

// convenience printer
template<size_t N, class T>
inline std::ostream& operator<<(std::ostream& os, const better_array<N, T>& a)
{
    os << "[";
    auto sep = " ";
    for (const auto& i : a) {
        os << sep << i;
        sep = ", ";
    }
    return os << " ]";
}

// for testing non-integrals
struct big_object
{
    std::string s = "defaulted";
    std::vector<std::string> v = { "defaulted1", "defaulted2" };
};

inline std::ostream& operator<<(std::ostream& os, const big_object& a)
{
    os << "{ s=" << quoted(a.s);
    os << ", v = [";
    auto sep = " ";
    for (const auto& s : a.v) {
        os << sep << quoted(s);
        sep = ", ";
    }
    return os << " ] }";
}

// test various uses of our new array
auto main() -> int
{
    using namespace std;

    // quick test
    better_array<3, int> x { 0, 3, 2 };
    cout << x << endl;

    // test that incomplete initialiser list results in a default-constructed object
    better_array<2, big_object> y { big_object { "a", { "b", "c" } } };
    cout << y << endl;

    // test constexpr construction using mutable array
    // question: how good is this optimiser anyway?
    cout << factorials<10>()[5] << endl;

    // answer: with apple clang7, -O3 the above line
    // compiles to:
    //  movq    __ZNSt3__14coutE@GOTPCREL(%rip), %rdi
    //  movl    $360, %esi              ## imm = 0x168
    //  callq   __ZNSt3__113basic_ostreamIcNS_11char_traitsIcEEElsEl
    // so that's pretty good!


    return 0;
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!