C++ 在编译时填充std::数组,并使用const_cast执行可能的未定义行为

C++ 在编译时填充std::数组,并使用const_cast执行可能的未定义行为,c++,c++14,undefined-behavior,constexpr,const-cast,C++,C++14,Undefined Behavior,Constexpr,Const Cast,众所周知,由于C++14是constexpr,请参见下面的声明: constexpr const_reference operator[]( size_type pos ) const; 但是,它也是合格的。如果要在编译时使用std::array的下标运算符为数组赋值,这会产生影响。例如,考虑以下用户文字: template<typename T, int N> struct FooLiteral { std::array<T, N> arr; constex

众所周知,由于C++14是
constexpr
,请参见下面的声明:

constexpr const_reference operator[]( size_type pos ) const; 
但是,它也是合格的。如果要在编译时使用
std::array
的下标运算符为数组赋值,这会产生影响。例如,考虑以下用户文字:

template<typename T, int N>
struct FooLiteral {
  std::array<T, N> arr;
  constexpr FooLiteral() : arr {} { for(int i(0); i < N; ++i) arr[i] = T{42 + i}; }
};

也就是说,我将数组强制转换为
const
引用以调用正确的下标运算符重载,然后我将重载的下标运算符的返回对象强制转换为
T&
,以便移除其常量并能够分配给它

这很好,但我知道应该谨慎使用
const\u cast
,坦率地说,对于这种黑客行为是否会导致未定义的行为,我有第二个想法

直观地说,我认为没有问题,因为这个
const\u cast
是在编译时初始化时发生的,因此,我想不出在这种状态下可能出现的含义

但这是真的吗,还是我错了,把UB引入了这个项目

问:
有人能证明这是否是UB吗?

这里没有UB,你的成员
arr
是非常量,你可以随意“玩”它的
常量(好吧,你明白我的意思了)


如果您的成员是一个常量表达式,那么您就有了UB,因为您已经在initializer列表中进行了初始化,并且在创建后不允许假定它具有可变值。在初始值设定项列表中执行任意元编程

据我所知,这不是未定义的行为。向
操作员[]
提出的建议发生在。所以看起来他们只是添加了constexpr,而没有考虑是否需要保持const

我们可以从早期版本中看到,它对常量表达式中的文字进行了如下更改:

在常量表达式中创建的对象可以在该常量表达式的求值过程中进行修改(包括对其进行的任何constexpr函数调用的求值),直到该常量表达式的求值结束,或者对象的生存期结束,以较早发生的为准。以后的常量表达式计算无法修改它们。[……]

这种方法允许在计算中发生任意变量突变,同时仍然保留了常量表达式计算独立于程序的可变全局状态的基本属性。因此,常量表达式无论何时求值,都会求值为相同的值,但未指定值的情况除外(例如,浮点计算可能会给出不同的结果,并且随着这些更改,不同的求值顺序也可能给出不同的结果)

我们可以看到我之前提到的提案指出了
const_cast
hack,它说:

在C++11中,constexpr成员函数隐式为const。这给希望在常量表达式内部和外部都可用的文本类类型带来了问题:

[……]

为解决此问题,提出了几种备选方案:

  • 接受现状,并要求用户通过const_cast解决这一小尴尬。

这不是对问题的直接回答,但希望是有用的

有一段时间我一直被std::array
困扰着,我决定看看是否只使用用户代码就可以做得更好

事实证明它可以:

#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;
}
#包括
#包括
#包括
#包括
#包括
#包括
//数组的完全constexpr版本,允许不完整的
//建筑
模板
struct\u数组
{
//公共构造函数服从于内部构造函数
//缺少参数的条件处理
constexpr\u数组(std::initializer\u列表)
:更好的数组(列表,标准::生成索引序列()
{
}
constexpr T&运算符[](大小i)无例外{
断言(i=list.size()
?
T()
:
std::move(*(std::next(list.begin(),Is)))
)...
}
{
}
};
//计算一个简单的阶乘作为constexpr
constexpr长阶乘(长x)
{

如果(x)您似乎说OP不能使用它初始化一个
constexpr-doubiteral
,对于
const-doubiteral
,成员不能被修改…为什么不呢,非常好!如果被接受,它提供了一个通用的替代方案
#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;
}