C++别名的模板参数包扩展

C++ template parameter pack expansion of alias

本文关键字:扩展 包扩展 参数 别名 C++      更新时间:2023-10-16

因此,我在这里的动机是确定多个类中的相同命名类型声明是否是相同的类型。 在此示例中,我希望看到所有 Foo、Bar 和 Baz 都有一个内部类型 Q。

#include <type_traits>
template <typename N,typename ...Ns>
using equal_type_t = typename std::enable_if_t<(std::is_same_v<N, Ns> && ...), N>;
template <typename N>
using ExtractQ_t = typename N::Q;
template <typename ...Ns>
using EqualQ_t = equal_type_t<ExtractQ_t<Ns>...>;

int main()
{
struct Qness{};
struct Foo{using Q = Qness;};
struct Bar{using Q = Qness;};
struct Baz{using Q = Qness;};
using F = EqualQ_t<Foo,Bar,Baz>;
static_assert(std::is_same_v<F,Qness>);
return 0;
}

在 clang9 中测试(赞美上帝螺栓(。

报告的错误是:

#1 with x86-64 clang 9.0.0
<source>:10:31: error: pack expansion used as argument for non-pack parameter of alias template
using EqualQ_t = equal_type_t<ExtractQ_t<Ns>...>;

我可能可以通过做一些模板递归来解决这个问题,但我正在尝试学习尽可能使用参数包扩展。

这可能吗? 这不是允许的上下文吗? 如果我分离出几个单独的 N 类型,它可以正常工作:

template <typename N1,typename N2, typename N3, typename ...Ns>
using EqualQ_t = equal_type_t<ExtractQ_t<N1>,ExtractQ_t<N2>,ExtractQ_t<N3>>;

我必须有一个咖啡前的脑雾,看不到我可能在哪里隐藏语法。

是否有可行的扩展变体?

错误诊断试图说equal_type_t的第一个参数不能是一个包,但你正在将一个包扩展到其中。因此,简单的解决方法是执行您之前所做的相同操作:

template <typename N, typename ...Ns>
using EqualQ_t = equal_type_t<ExtractQ_t<N>, ExtractQ_t<Ns>...>;

https://godbolt.org/z/j6_HGU

解包到非包 + 包中需要模板参数推断,但别名模板不会发生这种情况,请参阅 cpp首选项。您需要结构模板专用化(或模板函数调用(才能获得扣除。


不过,在这种情况下,使用 SFINAE 似乎有点奇怪。如果不满足条件,你会得到一些编译器关于SFINAE的胡言乱语。还有其他方法会导致编译过程中出现硬错误。

我想说的是,以下是编写相同代码的惯用方法,当出现问题时,它会给你一个很好的错误,并且会(不完全巧合(避免你原来的问题:

template <typename ...Ns>
struct equal_type;
template <typename N,typename ...Ns>
struct equal_type<N, Ns...>
{
static_assert((std::is_same_v<N, Ns> && ...), "These types must be the same!");
using type = N;
};
template <typename ...Ns>
using equal_type_t = typename equal_type<Ns...>::type;
template <typename N>
using ExtractQ_t = typename N::Q;
template <typename ...Ns>
using EqualQ_t = equal_type_t<ExtractQ_t<Ns>...>;

https://godbolt.org/z/u52mUE


为了完整起见,C++17 之前的方式(在折叠表达式存在之前(确实使用了递归:

template <typename N1, typename N2, typename ...Ns>
struct equal_type
{
static_assert(std::is_same_v<N1, N2>, "These types must be the same!");
using type = typename equal_type<N1, Ns...>::type;
};
template <typename N1, typename N2>
struct equal_type<N1, N2>
{
static_assert(std::is_same_v<N1, N2>, "These types must be the same!");
using type = N1;
};

https://godbolt.org/z/NKmMZD

认为这最适合自我回答,但这主要是针对马克斯的,他提出了一个返回问题,这个回答"太长了,不适合空白"。

如果我从未尝试过参数包,我可能不会在模板递归上使用这种变体来解决我的问题,但如果我没有接受过真正的问题教育,这可能是我可能会去的地方。

#include <type_traits>
template <typename N,typename ...Ns>
using equal_type_t = typename std::enable_if_t<(std::is_same_v<N, Ns> && ...), N>;
template <typename N>
using ExtractQ_t = typename N::Q;
template <typename N,typename ...Ns>
class EqualQ
{
public:
using type = equal_type_t<ExtractQ_t<N>,typename EqualQ<Ns...>::type>;
};
template <typename N>
class EqualQ<N>
{
public:
using type = ExtractQ_t<N>;
};
template <typename ...Ns>
using EqualQ_t = typename EqualQ<Ns...>::type;
int main()
{
struct Qness{};
struct Foo{using Q = Qness;};
struct Bar{using Q = Qness;};
struct Baz{using Q = Qness;};
using F = EqualQ_t<Foo,Bar,Baz>;
static_assert(std::is_same_v<F,Qness>);
return 0;
}

是的,我意识到这不是惯用语,绝对不如Max的任何解决方案干净,并且没有回答我最初的问题,而是探讨了我试图避免的内容。

不过,我确实发现以这种方式这样做的一件事是,别名不能像类模板那样专门化。 所以教育点也在那里。 我不得不将 EqualQ 转换为模板化结构。

问题是,这样做不会教育我为什么不能按照我最初想要的方式解压缩我的参数包,当然也不会了解我现在将采用的 Max 惯用模式。 :)