【问题标题】:Passing a std::shared_ptr<T> to a function that takes a std::shared_ptr<const T>?将 std::shared_ptr<T> 传递给采用 std::shared_ptr<const T> 的函数?
【发布时间】:2020-11-26 17:50:39
【问题描述】:

我有一个函数需要共享一个参数的所有权,但不修改它。 我已将参数设为 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'

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

  • 修改对 func 的调用。这是更大的代码重构的一部分,我不希望在每个调用站点都使用 std::const_pointer_cast。
  • 定义多个 func 重载似乎是多余的。

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

【问题讨论】:

  • 我认为模板的优先级更高,如果这将是完全输入的,它就可以工作。您可以添加一个重载,尽管这没有多大意义。只是为了确定,你需要一个 shared_ptr 而不是参考?
  • 是的,如果 func 不是模板,它就可以工作(这就是为什么我很惊讶它不适用于模板函数)。 func 正在共享指向对象的所有权,所以我不能引用 T。
  • Goh,预见将 const 添加到 shared_ptr 的免费方法将是我的实用解决方案,或者添加 2 个重载并在重定向之前显式添加 const。

标签: c++ c++11 c++14 shared-ptr


【解决方案1】:
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 的工作方式相匹配,“过载”是微不足道的转发器,成本几乎为零。

【讨论】:

  • 嗯。在这里我认为cbegin() 通常代表begin()
  • 为什么不... std::shared_ptr&lt;const T&gt; lp = std::move(ptr); 作为 func() 的第一行?
【解决方案2】:

不幸的是,没有好的解决方案可以满足您的需求。发生该错误是因为它无法推断出模板参数T。在论证推理过程中,它只尝试一些简单的对话,你不能以任何方式影响它。

想一想:从std::shared_ptr&lt;T&gt; 转换为某个std::shared_ptr&lt;const U&gt; 它需要知道U,那么编译器应该如何判断U=T 而不是其他类型?你总是可以投到std::shared_ptr&lt;const void&gt;,那为什么不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&lt;const T&gt; 的实现函数。甚至在调用 func_impl 时直接执行 const cast。

【讨论】:

    【解决方案3】:

    感谢您的回复。

    我最终以稍微不同的方式解决了这个问题。我将函数参数更改为只是 shared_ptr 到任何 T 以便它允许 const 类型,然后我使用 std::enable_if 将模板限制为我关心的类型。 (在我的情况下是 vector&lt;T&gt;const vector&lt;T&gt;

    调用站点无需修改。该函数将在同时使用 shared_ptr&lt;const T&gt;shared_ptr&lt;T&gt; 调用时进行编译,而无需单独的重载。

    这是一个在 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 版本的调用,并且两个版本都来自同一个模板。

    【讨论】:

      【解决方案4】:

      由于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));
      }
      

      但不保证编译器会消除这个动作。

      【讨论】:

        【解决方案5】:

        您当然不想修改调用站点,但您确实可以修改函数本身 - 这就是您在问题中所暗示的。毕竟,有些东西必须在某处进行更改。

        因此:

        在 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&lt;SomeType&gt; 啰嗦。

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

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

        【讨论】:

        • 是的,禁止将其添加到::std。不过,添加volatileconst volatile 变体怎么样?
        猜你喜欢
        • 2019-10-20
        • 2017-07-18
        • 1970-01-01
        • 2015-07-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多