将 std::shared_ptr<T> 传递给接受 std::shared_ptr<const T> 的函数?

Passing a std::shared_ptr<T> to a function that takes a std::shared_ptr<const T>?

我有一个函数需要共享参数的所有权,但不修改它。 我已经将论点设为 shared_ptr 以清楚地传达这一意图。

template <typename T>
void func(std::shared_ptr<const T> ptr){}

我想用一个 shared_ptr 来调用这个函数到一个非常量 T。例如:

auto nonConstInt = std::make_shared<int>();
func(nonConstInt);

然而,这会在 VC 2017 上产生编译错误:

error C2672: 'func': no matching overloaded function found
error C2784: 'void func(std::shared_ptr<const _Ty>)': could not deduce template argument for 'std::shared_ptr<const _Ty>' from 'std::shared_ptr<int>'
note: see declaration of 'func'

有没有办法使这项工作没有:

我们目前正在根据 C++14 标准进行编译,如果有帮助的话,我们计划很快迁移到 C++17。

很遗憾,没有很好的解决方案。发生错误是因为它无法推断模板参数 T。在论证推导过程中,它只会尝试一些简单的对话,您无法以任何方式影响它。

想一想:从 std::shared_ptr<T> 转换为某些 std::shared_ptr<const U> 它需要知道 U,那么编译器应该如何判断 U=T 而不是其他类型?您始终可以转换为 std::shared_ptr<const void>,那么为什么不转换为 U=void?所以这样的搜索根本不会执行,因为一般来说它是无法解决的。或许,假设有人可以提出一项功能,其中某些用户明确声明的转换会尝试进行参数推导,但它不是 C++ 的一部分。

唯一的建议是不写函数声明 const:

    template <typename T>
    void func(std::shared_ptr<T> ptr){}

您可以尝试通过将函数变成重定向来显示您的意图:

    template <typename T>
    void func(std::shared_ptr<T> ptr)
    {
           func_impl<T>(std::move(ptr));
    }

其中func_impl是接受一个std::shared_ptr<const T>的实现函数。或者甚至在调用 func_impl.

时直接执行 const cast
template <typename T>
void cfunc(std::shared_ptr<const T> ptr){
  // implementation
}
template <typename T>
void func(std::shared_ptr<T> ptr){ return cfunc<T>(std::move(ptr)); }
template <typename T>
void func(std::shared_ptr<const T> ptr){ return cfunc<T>(std::move(ptr)); }

这与 cbegin 的工作方式相符,“过载”是成本几乎为零的微不足道的转发器。

由于 const 用于文档,请将其作为评论:

template <class T>
void func(std::shared_ptr</*const*/ T> p) {
}

如果函数足够大以使其值得,您还可以委托给获取指向常量对象的指针的版本:

template <class T>
void func(std::shared_ptr</*const*/ T> p) {
    if (!std::is_const<T>::value) // TODO: constexpr
        return func<const T>(std::move(p));
}

虽然不能保证编译器会消除移动。

您当然不想修改调用站点,但您肯定可以自己修改函数 - 无论如何,这就是您在问题中暗示的内容。毕竟某处有些东西必须改变。

因此:

在 C++17 中,您可以使用演绎指南和修改调用点,但与使用强制转换相比,其侵入性较小。我相信语言律师可以就是否允许向 std 命名空间添加推导指南提出意见。至少我们可以将这些演绎指南的适用性限制在我们关心的类型上——它对其他类型不起作用。这是为了限制混乱的可能性。

template <typename T>
class allow_const_shared_ptr_cast : public std::integral_constant<bool, false> {};
template <typename T>
static constexpr bool allow_const_shared_ptr_cast_v = allow_const_shared_ptr_cast<T>::value;

template<>
class allow_const_shared_ptr_cast<int> : public std::integral_constant<bool, true> {};

template <typename T>
void func(std::shared_ptr<const T> ptr) {}

namespace std {
template<class Y> shared_ptr(const shared_ptr<Y>&) noexcept 
    -> shared_ptr<std::enable_if_t<allow_const_shared_ptr_cast_v<Y>, const Y>>;
template<class Y> shared_ptr(shared_ptr<Y>&&) noexcept
    -> shared_ptr<std::enable_if_t<allow_const_shared_ptr_cast_v<Y>, const Y>>;
}

void test() {
    std::shared_ptr<int> nonConstInt;
    func(std::shared_ptr(nonConstInt));
    func(std::shared_ptr(std::make_shared<int>()));
}

std::shared_ptr 肯定没有 std::const_pointer_cast<SomeType>.

冗长

这应该不会对性能产生任何影响,但修改调用站点确实很痛苦。

否则没有不涉及修改被调用函数声明的解决方案 - 但我认为修改应该是可以接受的,因为它并不比你已经拥有的更冗长:

感谢您的回复。

我最终以稍微不同的方式解决了这个问题。我将函数参数更改为 shared_ptr 到任何 T 以便它允许 const 类型,然后我使用 std::enable_if 将模板限制为我关心的类型。 (在我的例子中 vector<T>const vector<T>

调用站点不需要修改。该函数将在使用 shared_ptr<const T>shared_ptr<T> 调用时编译,无需单独重载。

这是在 VC、GCC 和 clang 上编译的完整示例:

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

template<typename T>
struct is_vector : public std::false_type{};

template<typename T>
struct is_vector<std::vector<T>> : public std::true_type{};

template<typename T>
struct is_vector<const std::vector<T>> : public std::true_type{};

template <typename ArrayType,
         typename std::enable_if_t<is_vector<ArrayType>::value>* = nullptr>
void func( std::shared_ptr<ArrayType> ptr) {
}

int main()
{
    std::shared_ptr< const std::vector<int> > constPtr;
    std::shared_ptr< std::vector<int> > nonConstPtr;
    func(constPtr);
    func(nonConstPtr);
}

唯一的缺点是 func 的非常量实例化将允许在传入的 ptr 上调用非常量方法。在我的例子中,编译错误仍然会产生,因为有一些对 func 的 const 版本的调用,并且两个版本都来自同一个模板。