c++ 如何用const成员最好地累积变量?

t0ybt7op  于 2023-11-19  发布在  其他
关注(0)|答案(3)|浏览(120)

下面的操作不起作用,因为Int有一个const成员,并且=操作符被删除,因为它不能修改值:

struct Int {
    const int val = 1;
    Int(int val) : val(val) {}
    operator int() {
        return val;
    }
};

std::vector<Int> ints = {1, 2, 3, 4};
Int sum = 0;
for (Int i : ints) { sum = sum+i; }
std::cout << sum;

字符串
如果我尝试使用std::accumulate,也会出现同样的问题。
我可以通过将sum设置为智能指针来解决这个问题,但它看起来很笨拙。有更好的方法吗?

gjmwrych

gjmwrych1#

如果我尝试使用std::accumulate,也会出现同样的问题。
不,它不会,如果你正确使用它。

#include <numeric>
#include <vector>
#include <iostream>
struct Int {
    const int val = 1;
    Int(int val) : val(val) {}
    operator int() {
        return val;
    }
};

int main()
{
    std::vector<Int> v{1,2,3,4};
    auto x = Int(std::accumulate(v.begin(),v.end(),0,[](int accu,const Int& i) { return accu + i.val;}));    
    std::cout << x;
}

字符串
Live Demo
返回类型和accu可以是与元素不同的类型。返回类型由初始值0确定,为int
你可以在你的手写循环中做同样的事情:

std::vector<Int> v{1,2,3,4};
Int sum = [&v](){
    int temp = 0;
    for (const auto& e : v) temp += e.val;
    return temp;
}();


如果你想修改成员,不要让他们成为const。虽然,你不需要修改它来累积它。而且,operator int应该是const
假设用例是我想删除const,但出于某种原因我不能。
那么解决的办法就是不要使用Int。它可以被认为是坏了不止一个原因。

aurhwmvo

aurhwmvo2#

我有一个类型的对象列表,其中有一些const成员,我想得到一个相同类型的对象,它是列表的某种积累,例如一个sum。
我并不是在寻找“使其成为非常数”的解决方案,也没有必要让这个问题过于具体。

概念上,这可以通过左(或右)折叠来完成,这只是一个递归调用,不涉及任何赋值。这里是cppreference page for fold_left_first in C++23

左折叠给定范围的元素,即返回链表达式的求值结果:f(f(f(x1,x2),x3),...),xn),
但是,标准库中的fold_left_first仍然需要std::movable<T>,而std::assignable_from<T&, T>又需要IntInt会因为这个约束而失败,所以即使我们有C++23也不能直接在这里使用。
不过,您也可以编写自己的fold_left_first版本,而不受这些限制,如下所示:

#include <functional>
#include <iostream>
#include <optional>
#include <ranges>
#include <vector>

struct Int {
    const int val = 1;
    Int(int val) : val(val) {}
    operator int() {
        return val;
    }
    Int operator+(const Int& other) const{
        return val + other.val;
    }
};

template <typename R, typename F>
auto fold_left_first(R&& range, F&& f){
    if(std::ranges::empty(range)){
        return std::optional<std::ranges::range_value_t<R>>{};
    }
    else if(std::ranges::size(range) == 1){
        return std::optional{*std::ranges::begin(range)};
    }
    else{
        return std::optional{
            std::invoke(std::forward<F>(f), 
                *fold_left_first(
                    std::ranges::subrange(std::ranges::begin(range), std::ranges::prev(std::ranges::end(range))), 
                    std::forward<F>(f)
                ), 
                *std::ranges::prev(std::ranges::end(range))
            )
        };
    }
}

int main(){
    std::vector<Int> ints = {1, 2, 3, 4};
    std::cout << fold_left_first(ints, std::plus<>{})->val << std::endl;
    //Also works for rvalue
    std::cout << fold_left_first(std::vector<Int>{1, 2, 3, 4}, std::plus<>{})->val << std::endl;
}

字符串
fold_left_first遵循与标准版本相同的语义,并返回std::optional以指示可能为空的范围。您可以调整它以满足您的需求。
然而,正如其他人评论的那样,通常建议避免使用const和reference成员,但既然你已经声明删除它们不是一个选项,这是我能得到的最好的而不失一般性。
演示:https://godbolt.org/z/Kojc1MvKr

vyswwuz2

vyswwuz23#

它只是一个概念(例如,缺少移动语义),但您可以创建一个 Package 器,该 Package 器根据需求/分配创建/销毁对象。
当然,如果构造函数/析构函数不便宜,这个解决方案也不会神奇地便宜。

#include <type_traits>
#include <memory>
#include <vector>
#include <iostream>

template <typename T>
struct Immutable {
    Immutable(T value) { new (&m_value) T(std::move(value)); }

    Immutable& operator=(const Immutable& o) {
        if (&o != this) {
            std::destroy_at(std::launder(reinterpret_cast<T*>(&m_value)));
            new (&m_value) T(o.m_value);
        }
        return *this;
    }
    Immutable& operator=(const T& o) {
        std::destroy_at(std::launder(reinterpret_cast<T*>(&m_value)));
        new (&m_value) T(o);

        return *this;
    }

    ~Immutable() {
        std::destroy_at(std::launder(reinterpret_cast<T*>(&m_value)));
    }

    operator T() const {
        return *std::launder(reinterpret_cast<const T*>(&m_value));
    }

    const T& value() const {
        return *std::launder(reinterpret_cast<const T*>(&m_value));
    }

    std::aligned_storage_t<sizeof(T), alignof(T)> m_value;
};

个字符

相关问题