我需要创建一个这样的模板函数:
template<typename T>
void foo(T a)
{
if (T is a subclass of class Bar)
do this
else
do something else
}
我也可以想象使用模板特化来做它...但我从未见过超类的所有子类的模板特化。我不想为每个子类重复专门化代码
答案 0 :(得分:14)
你可以做你想做的事,但不能做你想做的事!您可以将std::enable_if
与std::is_base_of
一起使用:
#include <iostream>
#include <utility>
#include <type_traits>
struct Bar { virtual ~Bar() {} };
struct Foo: Bar {};
struct Faz {};
template <typename T>
typename std::enable_if<std::is_base_of<Bar, T>::value>::type
foo(char const* type, T) {
std::cout << type << " is derived from Bar\n";
}
template <typename T>
typename std::enable_if<!std::is_base_of<Bar, T>::value>::type
foo(char const* type, T) {
std::cout << type << " is NOT derived from Bar\n";
}
int main()
{
foo("Foo", Foo());
foo("Faz", Faz());
}
由于这些东西得到了更广泛的传播,人们已经讨论过某种static if
,但到目前为止它还没有出现。
std::enable_if
和std::is_base_of
(在<type_traits>
中声明)都是C ++ 2011中的新功能。如果需要使用C ++ 2003编译器进行编译,可以使用Boost中的实现(需要将命名空间更改为boost
并包括"boost/utility.hpp"
和"boost/enable_if.hpp"
而不是相应的标准标题)。或者,如果你不能使用Boost,这两个类模板都可以很容易地实现。
答案 1 :(得分:4)
我会将std::is_base_of
和本地课一起用作:
#include <type_traits> //you must include this: C++11 solution!
template<typename T>
void foo(T a)
{
struct local
{
static void do_work(T & a, std::true_type const &)
{
//T is derived from Bar
}
static void do_work(T & a, std::false_type const &)
{
//T is not derived from Bar
}
};
local::do_work(a, std::is_base_of<Bar,T>());
}
请注意std::is_base_of
派生自std::integral_constant
,因此以前类型的对象可以隐式转换为后一种类型的对象,这意味着std::is_base_of<Bar,T>()
将转换为std::true_type
}或std::false_type
取决于T
的值。另请注意,std::true_type
和std::false_type
只是typedef,定义为:
typedef integral_constant<bool, true> true_type;
typedef integral_constant<bool, false> false_type;
答案 2 :(得分:3)
我喜欢这种清晰的风格:
void foo_detail(T a, const std::true_type&)
{
//do sub-class thing
}
void foo_detail(T a, const std::false_type&)
{
//do else
}
void foo(T a)
{
foo_detail(a, std::is_base_of<Bar, T>::value);
}
答案 3 :(得分:1)
问题是,实际上您无法在C ++ 17中执行以下操作:
template<T>
struct convert_t {
static auto convert(T t) { /* err: no specialization */ }
}
template<T>
struct convert_t<T> {
// T should be subject to the constraint that it's a subclass of X
}
但是,有两种方法可以使编译器根据涉及标签分派和SFINAE的类层次结构选择正确的方法。
让我们从标签分发开始。此处的关键是所选标签是指针类型。如果B
从A
继承,则为类型A*
的值选择B*
的重载:
#include <iostream>
#include <type_traits>
struct type_to_convert {
type_to_convert(int i) : i(i) {};
type_to_convert(const type_to_convert&) = delete;
type_to_convert(type_to_convert&&) = delete;
int i;
};
struct X {
X(int i) : i(i) {};
X(const X &) = delete;
X(X &&) = delete;
public:
int i;
};
struct Y : X {
Y(int i) : X{i + 1} {}
};
struct A {};
template<typename>
static auto convert(const type_to_convert &t, int *) {
return t.i;
}
template<typename U>
static auto convert(const type_to_convert &t, X *) {
return U{t.i}; // will instantiate either X or a subtype
}
template<typename>
static auto convert(const type_to_convert &t, A *) {
return 42;
}
template<typename T /* requested type, though not necessarily gotten */>
static auto convert(const type_to_convert &t) {
return convert<T>(t, static_cast<T*>(nullptr));
}
int main() {
std::cout << convert<int>(type_to_convert{5}) << std::endl;
std::cout << convert<X>(type_to_convert{6}).i << std::endl;
std::cout << convert<Y>(type_to_convert{6}).i << std::endl;
std::cout << convert<A>(type_to_convert{-1}) << std::endl;
return 0;
}
另一种选择是将SFINAE与enable_if
一起使用。这里的关键是,尽管问题开头的代码段无效,但该专业化不是:
template<T, typename = void>
struct convert_t {
static auto convert(T t) { /* err: no specialization */ }
}
template<T>
struct convert_t<T, void> {
}
因此,只要确保在任何给定点上只有一个有效,我们的专业就可以保留完全通用的第一个参数。为此,我们需要形成相互排斥的条件。示例:
template<typename T /* requested type, though not necessarily gotten */,
typename = void>
struct convert_t {
static auto convert(const type_to_convert &t) {
static_assert(!sizeof(T), "no conversion");
}
};
template<>
struct convert_t<int> {
static auto convert(const type_to_convert &t) {
return t.i;
}
};
template<typename T>
struct convert_t<T, std::enable_if_t<std::is_base_of_v<X, T>>> {
static auto convert(const type_to_convert &t) {
return T{t.i}; // will instantiate either X or a subtype
}
};
template<typename T>
struct convert_t<T, std::enable_if_t<std::is_base_of_v<A, T>>> {
static auto convert(const type_to_convert &t) {
return 42; // will instantiate either X or a subtype
}
};
template<typename T>
auto convert(const type_to_convert& t) {
return convert_t<T>::convert(t);
}
注意:问题文本中的特定示例可以用constexpr
解决,尽管:
template<typename T>
void foo(T a) {
if constexpr(std::is_base_of_v<Bar, T>)
// do this
else
// do something else
}
答案 4 :(得分:0)
我知道已经回答了这个问题,但是没有人提到std :: enable_if可以用作第二个模板参数,例如:
#include <type_traits>
class A {};
class B: public A {};
template<class T, typename std::enable_if<std::is_base_of<A, T>::value, int>::type = 0>
int foo(T t)
{
return 1;
}