将 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'
有没有办法使这项工作没有:
- 修改对 func 的调用。这是更大代码重构的一部分,我宁愿不必在每个调用站点都使用 std::const_pointer_cast。
- 定义 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 版本的调用,并且两个版本都来自同一个模板。
我有一个函数需要共享参数的所有权,但不修改它。
我已经将论点设为 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。
很遗憾,没有很好的解决方案。发生错误是因为它无法推断模板参数 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
.
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 版本的调用,并且两个版本都来自同一个模板。