如何私有地实现 STL 容器的运算符?

How do I privately implement an operator for an STL container?

提问人:Ders 提问时间:8/22/2023 最后编辑:Ders 更新时间:8/22/2023 访问量:63

问:

假设我们有这样的东西:

template<typename T>
class SparseMatrix {
    // Where the first (K,V) is (row_idx, columns) and the second is (col_idx, numeric type)  
    using Entries = std::unordered_map<unsigned long, std::unordered_map<unsigned long, T>>;
    Entries data;
    //...
};

对于稀疏矩阵,我们想忽略互为空的行,所以(我认为)我们可以使用如下方式实现加法:

template<typename K, typename V>
std::unordered_map<K, V> operator+(
    const std::unordered_map<K,V>& left, 
    const std::unordered_map<K,V>& right
) {
    std::unordered_map<K, V> result(left); 
    for (auto& p: result) {
        if (right.count(p.first) > 0) {
            result[p.first] += right[p.first]; // += def'n not shown...
        }
    }

    for (auto& p: right) {
        if (result.count(p.first) == 0) { 
            result[p.first] = p.second;
        }
    }

    return result;
} 

// ...

SparseMatrix::SparseMatrix operator+(const SparseMatrix& right)
{
    // checks and whatnot...
    // Uses the custom + operator: the "sum" of two unordered_maps!!!
    return this->data + right.data;
}

我认为这个模板很方便,因为它同时处理外部和内部操作;但是,由于这应该发生在头文件中,因此如果可能的话,我想将运算符定义私有化,因为它仅在那里有意义。我不想为任何 #includes 此实现的人进行明显的定义。unordered_mapSparseMatrixstd::unordered_map<K,V> operator+

除了全局声明 STL 容器的运算符定义之外,我还有什么惯用选项?

如果我上面起草的其他内容不是惯用的C++,我将不胜感激任何反馈。

天真地,我试图将该实现粘贴到 SparseMatrix 的私有部分,并遇到了一个错误,指出我为函数提供了太多参数。删除参数并将其替换为私有成员 data,则会导致未为 MatrixVals 定义 + 操作。我对这里发生的事情的假设可能是我引入了循环依赖关系。是这样吗?left

我没有找到任何其他 SO 帖子来描述如何以这种方式限制运算符重载的范围。

C++ 作用域 stl 运算符重载 无序映射

评论

3赞 Pepijn Kramer 8/22/2023
你应该有一个接受两个 SparceMatrix 实例的自由函数,而不是一个添加两个无序映射的自由函数(这个自由函数应该是你的 SparceMatrix 类的朋友,用于访问私有无序映射)
1赞 Mooing Duck 8/22/2023
为您不拥有的类添加运算符通常不是一个好主意。相反,最好只是为函数提供任何其他非运算符名称。
0赞 Mooing Duck 8/22/2023
显然你不能做一个.我可以声明它为 ,但在参数相关查找期间似乎找不到它。coliru.stacked-crooked.com/a/2462efb668d95b47operator+(unordered_map, unordered_map)staticfriend

答:

0赞 Mooing Duck 8/22/2023 #1

对于您不拥有的类,运算符通常不是一个好主意

但我们仍然可以做到。在类范围内制作它或在类范围内似乎不起作用,因此最好的方法似乎是将其放入私有命名空间中。staticfriend


namespace SparseMatrixImpl {
    
    template<typename K, typename V>
    std::unordered_map<K, V> operator+(
        std::unordered_map<K,V>& left, 
        std::unordered_map<K,V>& right
    ) {
       ...
    } 
}

然后在需要时将该函数拉入过载集

SparseMatrix::SparseMatrix operator+(const SparseMatrix& right)
{
    // checks and whatnot...
    // Uses the custom + operator: the "sum" of two unordered_maps!!!
    using SparseMatrixImpl::operator+;
    return this->data + right.data;
}

http://coliru.stacked-crooked.com/a/5f65464c73b727e0

更好的主意

就是简单地称它为其他东西,它是全局有用的,例如 .accumulate

    template<typename K, typename V>
    std::unordered_map<K, V> accumulate(
        std::unordered_map<K,V>& left, 
        std::unordered_map<K,V>& right
    ) {
       ...
    } 

http://coliru.stacked-crooked.com/a/c4b22e87354ccedf


此外,您在代码中还有各种其他错误和坏主意。也就是说,您正在尝试在“const”“right”上使用“operator[]”。

评论

0赞 Ders 8/22/2023
感谢您的回复。退后一步,你是对的,最好的方法是不要将运算符用于我不拥有的东西。感谢您更进一步,甚至尝试实施我的坏主意。目前,这解决了我试图解决的问题。
0赞 Ders 8/22/2023
godbolt.org/z/6eMhE3YbE根据我在 SO 上找到的其他一些示例,如果最后一个值类型是无序映射,则抛弃 += 并抛出一个编译时间条件以终止递归。感谢您的反馈。希望最后一个例子没有那么恶心,我正在收敛一些或多或少是传统的东西...... @Mooing鸭
0赞 Mooing Duck 8/22/2023
coliru.stacked-crooked.com/a/24c61d838bbb8962 可能是我会做的。通用映射方法(镜像 std::accumulate),其中运算符可以处理任意嵌套。请注意,这意味着我只做一次哈希查找。代码对每个键进行 2-3 次哈希查找。accumulatefind