智能指针上的模板推导/替换失败



让我们考虑一下这个代码:

template<typename T>
struct A
{
//...
};
struct B : public A<int>
{
//...
};
template<typename T>
bool validate(A<T>* p)
{
//...
return true;
};
int main()
{
A<int>* pA;
std::cout << validate(pA) << std::endl;
B* pB;
std::cout << validate(pB) << std::endl;
}

它编译正确,工作正常。现在,假设我需要重构代码以使用智能指针,那么validate也可以这样更改:

template<typename T>
bool validate(std::shared_ptr<A<T>> p)
{
//...
return true;
};
int main()
{
std::shared_ptr<A<int>> pA = std::make_shared<A<int>>();
validate(pA);  //it compiles correctly
std::shared_ptr<B> pB = std::make_shared<B>();
validate(pB);  //it FAILS to compile
}

你可以在这里验证。

这背后的原因是什么?

在不修改AB的情况下,解决此问题的最佳方法是什么?

这是因为它需要执行从shared_ptr<B>shared_ptr<A<int>>的自定义转换,以消除模板函数参数的歧义。消除模板函数参数的歧义甚至不尝试进行类型转换(除了一些基本的东西(。

甚至尝试都是不切实际的。好吧,理论上可能有一个部分解决方案,指定要尝试哪些自定义铸件,但没有。只需使用SFINEA并自己消除歧义,而不是要求编译器为您做这件事。

通常,如果被调用的函数不更改所有权,则应避免使用智能指针!使用原始指针函数。

在函数validate中强制使用泛型类型A。此处不考虑继承。

如果忽略继承,它可能看起来像:

template<typename T>
bool validate(std::shared_ptr<T> p)
{
return true;
}

参见Godbolt

为了强制创建基类,我将引入一个Type标记。

我认为可能的解决方法有:

备选方案1(

std::shared_ptr<B> pB = std::make_shared<B>();
//... do your type B related operations through pB
validate(std::shared_ptr<A<int>>(pB));

备选方案2(

template<typename T>
bool validate(A<T> const & a)
{
//...
return true;
}
int main()
{
std::shared_ptr<A<int>> pA = std::make_shared<A<int>>();
validate(*pA);
std::shared_ptr<B> pB = std::make_shared<B>();
validate(*pB);
}

备选方案3(

template<typename T>
bool validate(std::shared_ptr<A<T>> p)
{
//...
return true;
}
template<typename T>
bool validate(std::shared_ptr<T> p)
{
//...
return true;
}

int main()
{
std::shared_ptr<A<int>> pA = std::make_shared<A<int>>();
validate(pA); // it uses bool validate(std::shared_ptr<A<T>> p)
std::shared_ptr<B> pB = std::make_shared<B>();
validate(pB); // it uses bool validate(std::shared_ptr<T> p)
}

但这意味着该函数可能会扩展到许多其他类型,而且它不一定是期望的行为(也许?(。

还有其他建议吗?如果有人知道为什么问题中的代码一开始就不起作用,那就太酷了。

最新更新