重载解析和指向常量的共享指针

我正在转换一个大型代码,以使用定制的共享指针而不是原始指针。我对超载解决方案有问题。考虑这个例子:

#include <iostream>

struct A {};

struct B : public A {};

void f(const A*)
{
    std::cout << "const version
";
}

void f(A*)
{
    std::cout << "non-const version
";
}

int main(int, char**)
{
    B* b;
    f(b);
}

此代码正确写入"非常数版本",因为qualification conversions在隐式转换序列的排名中起作用。现在看一下使用SHARED_PTR:

的版本
#include <iostream>
#include<memory>

struct A {};

struct B : public A {};

void f(std::shared_ptr<const A>)
{
    std::cout << "const version
";
}

void f(std::shared_ptr<A>)
{
    std::cout << "non-const version
";
}

int main(int, char**)
{
   std::shared_ptr<B> b;
   f(b);
}

此代码无法编译,因为函数调用不明确。

我知道user-defined deduction-guide将是解决方案,但它仍然不存在于Visual Studio中。

我使用regexp转换代码,因为有数千个这样的调用。Regexp无法区分与常量版本匹配的调用和与非常量版本匹配的调用。是否有可能在使用共享指针时对重载解析进行更精细的控制,并避免手动更改每个调用?当然,我可以.get()原始指针并在调用中使用它,但我希望完全消除原始指针。


解决方案

您可以引入其他重载来为您执行延迟:

template <class T>
void f(std::shared_ptr<T> a)
{
  f(std::static_pointer_cast<A>(a));
}

template <class T>
void f(std::shared_ptr<const T> a)
{
  f(std::static_pointer_cast<const A>(a));
}

您还可以使用std::enable_if将第一个重载限制为非constT,和/或将两个重载限制为派生自AT

工作原理:

您对某些X有一个std::shared_ptr<X>,它既不是A也不是const A(它是Bconst B)。如果没有我的模板重载,编译器必须选择将这个std::shared_ptr<X>转换为std::shared_ptr<A>std::shared_ptr<const A>。这两种转换都是按等级进行的同样好的转换(都是用户定义的转换),因此存在歧义。

添加模板重载后,有四种参数类型可供选择(让我们分析一下X = const B案例):

  1. std::shared_ptr<A>
  2. std::shared_ptr<const A>
  3. std::shared_ptr<const B>从第一个模板实例化,T = const B
  4. std::shared_ptr<const B>从第二个模板实例化,T = B
显然,类型3和类型4比类型1和类型2要好,因为它们根本不需要转换。因此,将选择其中之一。

类型3和类型4本身是相同的,但随着模板的重载解析,会引入额外的规则。也就是说,与不太专业的模板相比,更专业的模板(更多的非模板签名匹配)是首选的。由于重载4在签名的非模板部分中具有const(在T之外),因此它更加专门化,因此被选择。

没有规则说"模板更好"。事实上,情况恰恰相反:当模板和非模板的成本相同时,非模板是首选的。这里的诀窍是,模板的成本(不需要转换)低于非模板(需要用户定义的转换)。

相关文章