我有一个函数需要获得参数的共享所有权,但不修改它。我已经使这个论点成为shared_ptr<const T>以明确传达这一意图。
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。
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)); }
这与CCD_ 1的工作方式相匹配;过载";是成本几乎为零的微不足道的货代。
不幸的是,没有一个好的解决方案可以满足您的需求。由于无法推导出模板参数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));
}
其中,cbegin
0是接受std::shared_ptr<const T>
的实现函数。甚至在调用func_impl
时直接执行const强制转换。
感谢您的回复。
我最终以一种稍微不同的方式解决了这个问题。我将函数参数从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版本的调用,并且两个版本都来自同一个模板。
由于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>()));
}
CCD_ 24肯定没有CCD_ 25那么冗长。
这应该不会对性能产生任何影响,但修改呼叫站点确实很痛苦。
否则,没有一个解决方案不涉及修改被调用的函数声明,但我认为修改应该是可以接受的,因为它不会比你已经拥有的更冗长: