与 SFINAE 中的类型检查有些混淆。假设有:
class Foo
{
public:
std::string member;
std::string member2;
template <
typename T, // Parameter 1.
typename U, // Parameter 2.
typename Enable = typename std::enable_if <
std::is_constructible<std::string, T>::value &&
std::is_constructible<std::string, U>::value>::type>
Foo(T&& member, U&& member2) :
member{std::forward<T>(member)},
member2{std::forward<U>(member2)}
{}
};
我不明白该使用什么,std::is_constructible或者std::is_convertible?std::is_same关于后者,据我了解,给我们提供了一个艰难的巧合,我们std::decay只能添加更多。但是,我想使用 C 数组或例如通过std::initializer_list. 我std::is_convertible知道它会跳过,例如,转换float为int,但在这段代码中:
template<class D>
impl_ptr(pointer p, D&& d,
typename std::enable_if<
std::is_convertible<D, deleter_type>::value,
dummy_t_
>::type = dummy_t_()) noexcept
: ptr_(std::move(p), std::forward<D>(d)) {}
被使用(这个例子是一个智能指针构造函数,用于实现 pimpl,D并且deleter_type是删除器,ptr_类型为std::unique_ptr)。
std::is_convertible意味着完全且仅隐式转换,所有附带的限制都强加于隐式转换。它不会考虑转换构造函数和标记为的转换运算符explicit。它不会在一个转换序列中执行一个以上的自定义转换。std::is_constructible直接考虑构造函数,不管它们是否被标记为explicit.T a(b)也就是这里的划分明确了,【大致】对应了直接初始化和拷贝初始化的区别T a = b。即使在 C++17 中,这些初始化形式之间的差异也不限于explicit.选择您的情况所需的确切内容。
您现在有了
is_constructible,它实际上提取了所有构造函数std::string,包括分配器中的构造。你需要这样的机会吗?我怀疑最初的意图是只允许构造各种字符串。然后再合适不过了is_convertible。在这种情况下,最好使用
std::is_constructible,因为您将参数传递给构造函数,这意味着您需要检查给定参数的构造函数是否存在。但是对于给出的具体示例,最好使用 string_view (如果可用),或者按值接受接收器并通过
std::move. 模板实现选项带来的问题多于其价值。而
std::is_convertible对于智能指针中的deleter,使用它是因为deleter可以是lambda,隐式转换为函数指针,但函数指针不是直接从lambda构造的。