IsConvertibleT
检测成员类型
实现一个traits,判断一个类型是否能够转换为另一个类型
#include <iostream>
#include <string>
#include <type_traits>
#include <utility>
template<typename FROM, typename TO>
class IsConvertibleHelper {
private:
// test() trying to call the helper aux(TO) for a FROM passed as F
static void aux(TO);
// decltype(aux(std::declval<F>())
// std::declval<F>() 返回一个右值引用
// SFINAE,如果 aux(std::declval<F>()) 合法,即可以调用aux,则表示可以转换,返回 std::true_type
template<typename F, typename T, typename = decltype(aux(std::declval<F>()))>
static std::true_type test(void*);
// fallback:
// 如果 aux(std::declval<F>()) 不合法,则表示不可以转换,返回 std::false_type
template<typename, typename>
static std::false_type test(...);
public:
using Type = decltype(test<FROM, TO>(nullptr));
};
template<typename FROM, typename TO>
struct IsConvertibleT : IsConvertibleHelper<FROM, TO>::Type {
};
// template<typename FROM, typename TO>
// using IsConvertible = typename IsConvertibleT<FROM, TO>::Type;
template<typename FROM, typename TO>
constexpr bool isConvertible = IsConvertibleT<FROM, TO>::value;
int main() {
std::cout << std::boolalpha;
std::cout << "int -> double: " << isConvertible<int,double><< std::endl;
std::cout << "int -> std::string: " << IsConvertibleT<int, std::string>::value << std::endl;
std::cout << "char const* -> std::string: " << IsConvertibleT<char const*, std::string>::value<< std::endl;
std::cout << "std::string -> char const*: " << isConvertible<std::string, char const*><< std::endl;
// std::cout << "int -> int: " << isConvertible<int, int> << std::endl;
// std::cout << "int -> int&: " <<isConvertible<int, int&> << std::endl;
}
上面的实现还有一些不足,没有考虑到类似数组等类型的匹配问题,细节可以参考std::is_convertible<>的实现。
Detecting Members 检测成员
给定一个类型T,查看是否有成员类型 size_type:
#include <iostream>
#include <string>
#include <type_traits>
#include <utility>
// helper to ignore any number of template parameters:
template<typename...>
using VoidT = void;
// primary template: 不匹配情况
template<typename, typename = VoidT<>>
struct HasSizeTypeT : std::false_type {};
// 偏特化:匹配情况 (may be SFINAE’d away)
// 在模板参数中声明T的成员T::size_type
// 根据SFINAE的特性,如果存在该成员类型,这个偏特化将会被选中,否则将会被忽略。
template<typename T>
struct HasSizeTypeT<T, VoidT<typename T::size_type>> : std::true_type {};
class A {
public:
using size_type = std::size_t; // 外部可访问
};
class B {
private:
using size_type = std::size_t; // 外部不可访问
};
int main() {
std::cout << std::boolalpha;
std::cout << "A has size_type: " << HasSizeTypeT<A>::value << '\n';
std::cout << "B has size_type: " << HasSizeTypeT<B>::value << '\n';
std::cout << "std::string has size_type: " << HasSizeTypeT<std::string>::value << '\n';
std::cout << "int has size_type: " << HasSizeTypeT<int>::value << '\n';
}
注意一个细节,如果size_type是私有的,那么template也无法访问,最终返回false.
所以这个trast的语义是判断是否有可访问的成员类型size_type
Note that if the member type size_type is private, HasSizeTypeT yields false because our traits templates have no special access to their argument type, and therefore typename T::size_type is invalid
但是,当传入的是一个引用类型时,最终会返回false
#include <iostream>
#include <string>
#include <type_traits>
#include <utility>
// helper to ignore any number of template parameters:
template<typename...>
using VoidT = void;
// primary template: 不匹配情况
template<typename, typename = VoidT<>>
struct HasSizeTypeT : std::false_type {};
// 偏特化:匹配情况 (may be SFINAE’d away)
// 在模板参数中声明T的成员T::size_type
// 根据SFINAE的特性,如果存在该成员类型,这个偏特化将会被选中,否则将会被忽略。
template<typename T>
struct HasSizeTypeT<T, VoidT<typename T::size_type>> : std::true_type {};
class A {
public:
using size_type = std::size_t;
};
int main() {
std::cout << std::boolalpha;
std::cout << "A has size_type: " << HasSizeTypeT<A>::value << '\n';
std::cout << "A& has size_type: " << HasSizeTypeT<A&>::value << '\n';
}
这本身没什么问题,引用类型本身并不包含成员(例如,int& 类型没有像 int 类型那样的大小成员)。但在实际使用引用时,引用表达式的值和行为体现的是底层类型(被引用类型)的性质。因此,在某些情况下,尤其是在检测一个类型是否拥有特定成员类型时,从实用角度出发,可能更倾向于考察引用所指向的底层类型是否拥有这个成员类型。当遇到引用类型时,应当考虑去除引用后其底层类型是否满足条件。
#include <iostream>
#include <string>
#include <type_traits>
#include <utility>
// helper to ignore any number of template parameters:
template<typename...>
using VoidT = void;
// primary template: 不匹配情况
template<typename, typename = VoidT<>>
struct HasSizeTypeT : std::false_type {};
// 偏特化版本:先移除引用,再查找size_type成员类型
// template<typename T>
// struct HasSizeTypeT<T, VoidT<typename std::remove_reference<T>::type::size_type>> : std::true_type {};
// 与上面等价
template<typename T>
struct HasSizeTypeT<T, VoidT< typename std::remove_reference_t<T>::size_type>> : std::true_type {};
class A {
public:
using size_type = std::size_t;
};
int main() {
std::cout << std::boolalpha;
std::cout << "A has size_type: " << HasSizeTypeT<A>::value << '\n';
std::cout << "A& has size_type: " << HasSizeTypeT<A&>::value << '\n';
std::cout << "A&& has size_type: " << HasSizeTypeT<A&&>::value << '\n';
}
检测任意成员类型
一种实现方式是使用宏(macros),
#include <iostream>
#include <string>
#include <type_traits>
// helper to ignore any number of template parameters:
template<typename...>
using VoidT = void;
// 使用宏来实现对任意类型的检测
// 用于生成一个模板结构体 HasTypeT_MemType,
// 这个结构体的作用是用来检测一个类型 T 是否具有指定名称的成员类型 MemType。
// 这里的 MemType 是一个占位符,将在实际使用时替换为具体的成员类型名称。
// ##MemType 表示这地方会被替换为具体的成员类型名称。
#define DEFINE_HAS_TYPE(MemType) \
template<typename, typename = std::void_t<>> \
struct HasTypeT_##MemType : std::false_type { }; \
template<typename T> \
struct HasTypeT_##MemType<T, std::void_t<typename std::remove_reference_t<T>::MemType>> : std::true_type { }
class A {
public:
using size_type = std::size_t;
using value_type = int;
// no iterator
};
DEFINE_HAS_TYPE(size_type); // 定义一个 HasTypeT_size_type 类型,判断 A 是否有 size_type 类型成员。
DEFINE_HAS_TYPE(value_type); // 定义一个 HasTypeT_value_type 类型,判断 A 是否有 value_type 类型成员。
DEFINE_HAS_TYPE(iterator); // 定义一个 HasTypeT_iterator 类型,判断 A 是否有 iterator 类型成员。
DEFINE_HAS_TYPE(first); //
int main() {
std::cout << std::boolalpha;
std::cout << "A has size_type: " << HasTypeT_size_type<A>::value << '\n';
std::cout << "A has value_type: " << HasTypeT_value_type<A>::value << '\n';
std::cout << "A has iterator: " << HasTypeT_iterator<A>::value << '\n';
std::cout << "A& has size_type: " << HasTypeT_size_type<A&>::value << '\n';
std::cout << "A& has value_type: " << HasTypeT_value_type<A&>::value << '\n';
std::cout << "A& has iterator: " << HasTypeT_iterator<A&>::value << '\n';
}
使用宏来生成对应的traits。
检测成员函数
类似于检测成员类型,在模板参数中使用std::declval()构造一个右值对象,并调用begin(), 根据SFINAE的规则进行匹配,不同与成员类型,这里引用包含了函数成员,所以不用对类型进行转换。
#include <iostream>
#include <string>
#include <type_traits>
#include <vector>
// helper to ignore any number of template parameters:
template<typename...>
using VoidT = void;
// primary template: 不匹配情况
template<typename, typename = VoidT<>>
struct HasBeginFunc : std::false_type {};
// 判断begin函数是否声明
template<typename T>
struct HasBeginFunc<T, VoidT<decltype(std::declval<T>().begin())>> : std::true_type {};
class A {
public:
void begin();
};
int main() {
std::cout << std::boolalpha;
std::cout << HasBeginFunc<A>::value << std::endl;
std::cout << HasBeginFunc<A&>::value << std::endl;
std::cout << HasBeginFunc<A&&>::value << std::endl;
std::cout << HasBeginFunc<int>::value << std::endl;
std::cout << HasBeginFunc<std::string>::value << std::endl;
std::cout << HasBeginFunc<std::vector<int>>::value << std::endl;
return 0;
}
检测其他表达式
结合上述方法,可以扩展为对很多表达式的检测,比如可以构造trait,用于检测类型T1 和 类型T2 之间的 < 比较符号是否合法:
#include <iostream>
#include <string>
#include <type_traits>
#include <vector>
// helper to ignore any number of template parameters:
// 其实就是 std::void_t
template<typename...>
using VoidT = void;
// primary template: 不匹配情况
template<typename, typename, typename = VoidT<>>
struct HasLessT : std::false_type {};
// 判断 < 操作符是否是配T1 和 T2 类型
template<typename T1, typename T2>
struct HasLessT<T1, T2,
VoidT<decltype(std::declval<T1>() < std::declval<T2>())> > : std::true_type {};
class A {
public:
void begin();
};
int main() {
std::cout << std::boolalpha;
std::cout << HasLessT<int,char>::value << std::endl;
std::cout << HasLessT<int,double>::value << std::endl;
std::cout << HasLessT<A,int>::value << std::endl;
return 0;
}
上述检查的traits, 均可以使用lambda的方式改写:
// define to check for <:
constexpr auto hasLess
= isValid([](auto x, auto y) -> decltype(valueT(x) < valueT(y)) {
});