使用派生模板 class 和智能指针进行模板推导
Template deduction with derived templated class and smart pointers
假设我有一个模板化的基础 class,以及一个从中派生的模板化的 class:
template <typename T>
class Base {};
template <typename T>
class Derived : public Base<T> {};
此外,我有一个函数想要接受指向任何 Base<T>
或子 class 的共享指针,并且能够轻松地使用 T
参数作为一部分其签名:
template <typename T>
T DoSomething(std::shared_ptr<Base<T>>);
我希望能够调用它,使用推导的 T
,使用指向 Base<T>
或任何派生自它的共享指针:
DoSomething(std::make_shared<Base<T>>());
DoSomething(std::make_shared<Derived<T>>());
当然后者不行,因为类型推导失败
如何修改 DoSomething
的签名使其生效?在 Base
和 Derived
不是模板的情况下,我已经看到了很多答案,但是如果我仍然想推断 T
(对于例如将其用作 return 类型,如上所述)。
理想情况下,在重载解析时,对于指向非派生输入(和非共享指针)的共享指针,这将失败。
您可以在函数中使用模板模板参数,然后 static_assert
来执行您的要求。
#include <memory>
#include <vector>
template <typename T>
class Base {};
template <typename T>
class Derived : public Base<T> {};
template <typename T, template <typename> typename U>
T DoSomething(std::shared_ptr<U<T>>) {
static_assert(std::is_base_of_v<Base<T>, U<T>>, "Requires a std::shared_ptr to Base of class derived from Base");
return T{};
}
int main() {
auto foo = std::make_shared<Derived<int>>();
auto baz = std::make_shared<Base<int>>();
auto bar = std::make_shared<std::vector<int>>();
DoSomething(foo);
DoSomething(baz);
DoSomething(bar); // Fails, std::vector<int> is not derived from Base<int>
}
编辑
如果 DoSomething
过载,我们可以使用 SFINAE 来禁用它而不是 static_assert
。看起来如下。
#include <memory>
#include <vector>
template <typename T>
class Base {};
template <typename T>
class Derived : public Base<T> {};
template <typename T, template <typename> typename U, std::enable_if_t<std::is_base_of_v<Base<T>, U<T>>, int> = 0>
T DoSomething(std::shared_ptr<U<T>>) {
return T{};
}
int main() {
auto foo = std::make_shared<Derived<int>>();
auto baz = std::make_shared<Base<int>>();
auto bar = std::make_shared<std::vector<int>>();
DoSomething(foo);
DoSomething(baz);
DoSomething(bar); // Error, no matching function
}
尽管如此,@super 已经接受了这个问题的答案,这里是使用 SFINAE 的一种略有不同(但可能不太优雅的方式):
#include <memory>
#include <type_traits>
template <typename T>
class Base {};
template <typename T>
class Derived : public Base<T> {};
template <template <typename> typename U, typename T>
std::enable_if_t<std::is_base_of<Base<T>,U<T>>::value, T>
DoSomething(std::shared_ptr<U<T>>)
{
return T{};
}
int main()
{
auto foo = std::make_shared<Base<int>>();
auto bar = std::make_shared<Derived<int>>();
DoSomething(foo);
DoSomething(bar);
return 0;
}
现场演示:godbolt。
为什么我认为这不太优雅?这是因为函数的签名已更改,现在 std::enable_if_t<...>
为 return 类型,即使它最终被推断为 T
(当然是为了有效输入)。
假设我有一个模板化的基础 class,以及一个从中派生的模板化的 class:
template <typename T>
class Base {};
template <typename T>
class Derived : public Base<T> {};
此外,我有一个函数想要接受指向任何 Base<T>
或子 class 的共享指针,并且能够轻松地使用 T
参数作为一部分其签名:
template <typename T>
T DoSomething(std::shared_ptr<Base<T>>);
我希望能够调用它,使用推导的 T
,使用指向 Base<T>
或任何派生自它的共享指针:
DoSomething(std::make_shared<Base<T>>());
DoSomething(std::make_shared<Derived<T>>());
当然后者不行,因为类型推导失败
如何修改 DoSomething
的签名使其生效?在 Base
和 Derived
不是模板的情况下,我已经看到了很多答案,但是如果我仍然想推断 T
(对于例如将其用作 return 类型,如上所述)。
理想情况下,在重载解析时,对于指向非派生输入(和非共享指针)的共享指针,这将失败。
您可以在函数中使用模板模板参数,然后 static_assert
来执行您的要求。
#include <memory>
#include <vector>
template <typename T>
class Base {};
template <typename T>
class Derived : public Base<T> {};
template <typename T, template <typename> typename U>
T DoSomething(std::shared_ptr<U<T>>) {
static_assert(std::is_base_of_v<Base<T>, U<T>>, "Requires a std::shared_ptr to Base of class derived from Base");
return T{};
}
int main() {
auto foo = std::make_shared<Derived<int>>();
auto baz = std::make_shared<Base<int>>();
auto bar = std::make_shared<std::vector<int>>();
DoSomething(foo);
DoSomething(baz);
DoSomething(bar); // Fails, std::vector<int> is not derived from Base<int>
}
编辑
如果 DoSomething
过载,我们可以使用 SFINAE 来禁用它而不是 static_assert
。看起来如下。
#include <memory>
#include <vector>
template <typename T>
class Base {};
template <typename T>
class Derived : public Base<T> {};
template <typename T, template <typename> typename U, std::enable_if_t<std::is_base_of_v<Base<T>, U<T>>, int> = 0>
T DoSomething(std::shared_ptr<U<T>>) {
return T{};
}
int main() {
auto foo = std::make_shared<Derived<int>>();
auto baz = std::make_shared<Base<int>>();
auto bar = std::make_shared<std::vector<int>>();
DoSomething(foo);
DoSomething(baz);
DoSomething(bar); // Error, no matching function
}
尽管如此,@super 已经接受了这个问题的答案,这里是使用 SFINAE 的一种略有不同(但可能不太优雅的方式):
#include <memory>
#include <type_traits>
template <typename T>
class Base {};
template <typename T>
class Derived : public Base<T> {};
template <template <typename> typename U, typename T>
std::enable_if_t<std::is_base_of<Base<T>,U<T>>::value, T>
DoSomething(std::shared_ptr<U<T>>)
{
return T{};
}
int main()
{
auto foo = std::make_shared<Base<int>>();
auto bar = std::make_shared<Derived<int>>();
DoSomething(foo);
DoSomething(bar);
return 0;
}
现场演示:godbolt。
为什么我认为这不太优雅?这是因为函数的签名已更改,现在 std::enable_if_t<...>
为 return 类型,即使它最终被推断为 T
(当然是为了有效输入)。