如何定义is_instantiable类型特征?C++技术实现问询
嘿,咱们来搞定这个is_instantiable类型特征的实现!首先得明确这里的“可被实例化”到底指什么——根据你的描述,它不需要指定构造参数,就能判断类型T能不能被创建出实例(不管用什么构造方式),这和std::is_constructible必须明确参数列表的特性确实不一样。
下面我会分步骤给出实现方案,同时兼顾不同C++版本的兼容性:
核心判断逻辑
一个类型T能被实例化,需要满足这几个条件:
- 它是对象类型:排除
void、函数类型、引用类型这些本质上不能作为对象的类型; - 它是完整类型:前向声明的不完整类没法实例化;
- 它不是抽象类:抽象类不能直接实例化;
- 它存在至少一种可访问的构造方式:不管是默认构造、拷贝/移动构造,还是带参数的构造函数。
第一步:实现is_complete类型特征(兼容C++17及更早版本)
C++20标准库提供了std::is_complete,但如果要兼容更早的版本,我们可以自己实现一个:
#include <type_traits> template <class T, class = void> struct is_complete : std::false_type {}; template <class T> struct is_complete<T, std::void_t<decltype(sizeof(T))>> : std::true_type {}; template <class T> inline constexpr bool is_complete_v = is_complete<T>::value;
这个实现利用了sizeof只能作用于完整类型的特性,通过SFINAE(替换失败不是错误)的机制来判断类型是否完整。
第二步:检测是否存在可访问的构造方式
这部分是难点——因为我们要检测的是任意一组参数能否构造T,而不是特定参数。这里提供两种思路:
思路1:覆盖常见构造场景(简单可靠)
如果一个类型能被默认构造、拷贝构造或者移动构造,那它肯定能被实例化。这种方式实现简单,能覆盖绝大多数日常场景:
template <class T> constexpr bool has_any_constructor_v = std::is_default_constructible_v<T> || std::is_copy_constructible_v<T> || std::is_move_constructible_v<T>;
缺点是会漏掉那些只有非默认、非拷贝/移动构造函数的类型(比如struct A { A(int); };)。
思路2:尝试多种参数构造(扩展性更强)
我们可以通过SFINAE尝试用不同类型的参数构造T,覆盖更多构造场景:
#include <utility> // 用于std::declval // 测试是否能用指定参数构造T template <class T, class... Args> constexpr auto test_construction(int) -> decltype(T(std::declval<Args>()...), std::true_type{}); template <class T, class... Args> constexpr std::false_type test_construction(...); // 检测是否存在至少一种构造方式(这里尝试了默认、拷贝、移动、int参数构造,可扩展更多类型) template <class T, class = void> struct has_any_constructor : std::false_type {}; template <class T> struct has_any_constructor<T, std::void_t< decltype(test_construction<T>(0)), // 默认构造 decltype(test_construction<T, const T&>(0)),// 拷贝构造 decltype(test_construction<T, T&&>(0)), // 移动构造 decltype(test_construction<T, int>(0)) // int参数构造 >> : std::true_type {}; template <class T> inline constexpr bool has_any_constructor_v = has_any_constructor<T>::value;
这种方式能检测到带参数构造的类型,你还可以在void_t中添加更多参数类型的测试(比如float、std::string等),进一步扩展覆盖范围。
第三步:组合条件实现最终的is_instantiable
现在把所有条件组合起来,得到最终的类型特征:
基于思路1的实现(兼容C++17)
#include <type_traits> // 实现is_complete template <class T, class = void> struct is_complete : std::false_type {}; template <class T> struct is_complete<T, std::void_t<decltype(sizeof(T))>> : std::true_type {}; template <class T> inline constexpr bool is_complete_v = is_complete<T>::value; // 检测常见构造方式 template <class T> constexpr bool has_any_constructor_v = std::is_default_constructible_v<T> || std::is_copy_constructible_v<T> || std::is_move_constructible_v<T>; // 最终的is_instantiable template <class T> struct is_instantiable : std::bool_constant< std::is_object_v<T> && is_complete_v<T> && !std::is_abstract_v<T> && has_any_constructor_v<T> > {}; template <class T> inline constexpr bool is_instantiable_v = is_instantiable<T>::value;
基于思路2的实现(兼容C++17)
#include <type_traits> #include <utility> // 实现is_complete template <class T, class = void> struct is_complete : std::false_type {}; template <class T> struct is_complete<T, std::void_t<decltype(sizeof(T))>> : std::true_type {}; template <class T> inline constexpr bool is_complete_v = is_complete<T>::value; // 测试构造函数 template <class T, class... Args> constexpr auto test_construction(int) -> decltype(T(std::declval<Args>()...), std::true_type{}); template <class T, class... Args> constexpr std::false_type test_construction(...); // 检测任意构造方式 template <class T, class = void> struct has_any_constructor : std::false_type {}; template <class T> struct has_any_constructor<T, std::void_t< decltype(test_construction<T>(0)), decltype(test_construction<T, const T&>(0)), decltype(test_construction<T, T&&>(0)), decltype(test_construction<T, int>(0)) >> : std::true_type {}; template <class T> inline constexpr bool has_any_constructor_v = has_any_constructor<T>::value; // 最终的is_instantiable template <class T> struct is_instantiable : std::bool_constant< std::is_object_v<T> && is_complete_v<T> && !std::is_abstract_v<T> && has_any_constructor_v<T> > {}; template <class T> inline constexpr bool is_instantiable_v = is_instantiable<T>::value;
测试用例验证
我们可以用几个典型例子来验证效果:
// 基础类型:可实例化 static_assert(is_instantiable_v<int> == true); static_assert(is_instantiable_v<double> == true); // 普通类:可实例化 struct NormalClass {}; static_assert(is_instantiable_v<NormalClass> == true); // 带int参数构造的类:思路2返回true,思路1返回false struct ParamClass { ParamClass(int) {} }; static_assert(is_instantiable_v<ParamClass> == true); // 仅思路2成立 // 抽象类:不可实例化 struct AbstractClass { virtual void f() = 0; }; static_assert(is_instantiable_v<AbstractClass> == false); // 不完整类型:不可实例化 struct IncompleteClass; static_assert(is_instantiable_v<IncompleteClass> == false); // 完全不可构造的类:不可实例化 struct NonConstructible { NonConstructible() = delete; NonConstructible(const NonConstructible&) = delete; NonConstructible(NonConstructible&&) = delete; }; static_assert(is_instantiable_v<NonConstructible> == false); // void类型:不可实例化 static_assert(is_instantiable_v<void> == false); // 引用类型:不可实例化 static_assert(is_instantiable_v<int&> == false); // 函数类型:不可实例化 static_assert(is_instantiable_v<void()> == false);
额外说明
- 如果你的实际需求是检测是否可以默认构造,那
std::is_default_constructible就完全够用,它和std::is_constructible<T>是等价的,但你明确要求和is_constructible的参数指定方式不同,所以上面的方案更贴合你的需求。 - 对于C++20及以上版本,可以直接用标准库的
std::is_complete替换我们自己实现的版本,简化代码。
内容的提问来源于stack exchange,提问作者Vincent
相关产品推荐
相关产品推荐

