C++概念:检查模板实例化

14
假设我有一个模板类型,例如:
template<typename A, typename B, typename C>
struct mytype { };

如何编写一个概念来检查一个类型是否是该模板的实例化?
template<typename T>
concept MyType = requires(T x) { ??? }

我找不到一个明显的方法来做到这一点,除非使用旧式的专用探测器类型或者可能是基于标记的基础类型。

我不确定你在问什么。这些概念还没有被纳入标准,或者是我错过了吗? - 463035818_is_not_a_number
1
这有什么用处? - cpplearner
@MrMobster:这不是概念的实质。你谈论的是让一个特定使用场景更方便。概念是找到一种表达对一个或多个模板参数能力约束的方法。也就是说,你不是将模板限制为特定类型;而是将模板限制为特定接口,多种类型(理论上)都可以满足。仅仅因为不想多打几个字而使用这种机制并不是它存在的意义所在。 - Nicol Bolas
@NicolBolas 我在谈论类型安全。而这正是概念的全部意义——填补 C++ 类型系统中的一个巨大空缺,即模板不是真正的类型,而是一种依赖于鸭子类型的宏构造。当然,概念的主要优势在于它们提供了围绕公共 API 的类型安全(类似于 Swift 和 Rust 中的协议和特征),但我也看不到为什么不能使用它们来提供针对参数化类型的单个实现的类型安全。 - MrMobster
@MrMobster:更好的代码?是的。首先,它避免了我所概述的断开连接。但同样重要的是,它让你停下来问一下……如果do_stuff没有使用那些模板参数,为什么我想要我的模板do_stuff函数只接受一个mytype实例化?为什么我不写一个适当的概念接口,让do_stuff可以针对它编写?一个不明确依赖于mytype本身的接口?这不像range::sort是针对vector特定地编写的;它是针对任何随机访问范围编写的;vector仅提供了该接口。 - Nicol Bolas
显示剩余6条评论
5个回答

11
您可以定义自己的元函数(类型特性)来实现这个目的:
template <typename T>
struct is_mytype : std::false_type { };

template <typename A, typename B, typename C>
struct is_mytype<mytype<A, B, C>> : std::true_type { };

template <typename T>
concept MyType = is_mytype<T>::value;

但说实话,我不知道是否有一种直接定义这种概念的方法,而无需单独的元函数。


是的,那也是我的解决方案。我希望概念能够使这些类型的检测器过时。 - MrMobster
@MrMobster 当你写好它们之后,它就可以了。嘿嘿。 - Lightness Races in Orbit

11
使用C++17类模板参数推导,您应该能够像这样做:
template<typename A, typename B, typename C>
struct mytype { };

template<class T>
concept C1 = requires(T x) { 
    { mytype{x} } -> std::same_as<T>;
};

mytype{x} 使用类模板参数推导来推断 ABC,所以如果你可以从 T 构造出一个 mytype<A, B, C>,那么这是有效的。特别地,如果 mytype 是可复制构造的,因为你有一个类似于 copy-deduction guide 的隐式声明。

template <typename A, typename B, typename C>
mytype(mytype<A, B, C>) -> mytype<A, B, C>;

检查T是否也是构造的mytype实例,可以避免匹配其他的推导指南,例如,对于任何没有-> std::same_as<T>的类型都会匹配:

template <class A, class B, class C>
struct mytype {
    mytype(A);
};

template <class A>
mytype(A) -> mytype<A, A, A>;

提议的解决方案对于不可复制构造的类无效,即使对于只能移动的类也应该可以使其工作。

使用进行测试:https://godbolt.org/z/ojdcrYqKv


但是谁能说A、B和C可以这样推断呢? - einpoklum
@einpoklum:总会有一个隐式生成的复制推导指南。即使有一个(隐式)删除的复制构造函数,这个指南也存在,但在这种情况下概念会失败(这在我的答案中有说明)。 - Holt
你的意思是,一个扣除指南,它能够准确地推断出那些实例化了模板的类型T中的A、B和C吗? - einpoklum
假设 mytype 有一个未删除的复制构造函数,那么你将拥有一个类似于以下的隐式生成的推导指南:template <class A, class B, class C> mystruct(mystruct<A, B, C> const&) -> mystruct<A, B, C>;,这是复制推导指南。 - Holt
1
此解决方案不符合C++20标准。在requires表达式中,返回类型要求应该是一个概念(concept),而不是一个类型。请将 "-> T" 替换为 "-> std::same_as<T>"。 - Björn Sundin
显示剩余2条评论

4

您可以编写一个通用的trait来检查特殊化:

template <typename T, template <typename...> class Z>
struct is_specialization_of : std::false_type {};

template <typename... Args, template <typename...> class Z>
struct is_specialization_of<Z<Args...>, Z> : std::true_type {};

template <typename T, template <typename...> class Z>
inline constexpr bool is_specialization_of_v = is_specialization_of<T,Z>::value;

您可以将其转化为一个通用的概念:

template<typename T, template <typename...> class Z>
concept Specializes = is_specialization_of_v<T, Z>;

template<typename T>
concept MyType = Specializes<T, mytype>;

或者只是一个专业的。
template<typename T>
concept MyType = is_specialization_of_v<T, mytype>;

2
它并不是真正的通用,因为它只适用于本身采用类型的模板类。因此,您无法询问它是否是std::array甚至std::span的特化,因为这些采用非类型模板参数。 - Nicol Bolas
@NicolBolas 绝大多数的模板只接受类型参数。所以我认为它是相当通用的。但是是的,它在这两个上不起作用。 - Barry
4
http://open-std.org/JTC1/SC22/WG21/docs/papers/2020/p2098r1.pdf - pooya13

2
为了简洁起见:
template<typename T>
concept MyType = requires(T** x) {
    []<typename A, typename B, typename C>(mytype<A, B, C>**){}(x);
};

双指针是必要的,以避免派生到基类转换,例如: struct S : mytype<int, int, int> {}。目前在clang中无法运行,因为它不允许未计算上下文中的lambda表达式。您可以通过提供一个辅助变量模板来解决问题。
template<class T> constexpr auto L = []<typename A, typename B, typename C>(mytype<A, B, C>**){};
template<typename T>
concept MyType = requires(T** x) { L<T>(x); };

只要mytype的模板参数都是类型,您可以使用占位符使其更加简洁:
template<typename T>
concept MyType = requires(T** x) { [](mytype<auto, auto, auto>**){}(x); };

目前,这只适用于gcc。


太棒了,谢谢!我使用了最后一个,但只使用单指针,因为我想能够检测到模板类型的派生类。这少了14行代码。 \o/ - underscore_d
1
似乎在函数参数类型的模板参数中使用auto并不是标准的C++20,尽管GCC默默地允许它 :-(。然而,Clang却不允许,并且这个帖子表明这只是概念TS中的内容,但不是有效的标准C++20。那么,GCC的-std=c++20标志就无效了。 :-/ 我已经重写了没有使用auto的代码。 - underscore_d

0
如果您给模板类添加一些特性,您可以执行以下操作:
template<typename A, typename B, typename C>
struct mytype {
    using a_type = A;
    using b_type = B;
    using c_type = C;
};

伴随着关联概念:

template <typename T>
concept is_mytype =
    std::is_same_v<
        std::remove_const_t<T>,
        mytype<typename T::a_type, typename T::b_type, typename T::c_type>>;

或者,如果mytype具有这些类型的成员,则可以跳过特征:

template<typename A, typename B, typename C>
struct mytype {
    A a_inst;
    B b_inst;
    C c_inst;
};

给出概念:
template <typename T>
concept is_mytype =
    std::is_same_v<
        std::remove_const_t<T>,
        mytype<decltype(T::a_inst), decltype(T::b_inst), decltype(T::c_inst)>>;

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接