【问题标题】:How to emulate deduction guides for template aliases?如何模拟模板别名的推导指南?
【发布时间】:2019-01-13 20:44:56
【问题描述】:

考虑以下几点:

template <typename T, std::size_t N>
struct my_array
{
    T values[N];
};

我们可以为my_array提供推演指南,比如

template <typename ... Ts>
my_array (Ts ...) -> my_array<std::common_type_t<Ts...>, sizeof...(Ts)>;

现在,假设my_array&lt;T, 2&gt; 有一些非常特殊的含义(但只是含义,接口和实现保持不变),所以我们想给它一个更合适的名称:

template <typename T>
using special = my_array<T, 2>;

It turns out 推导指南根本不适用于模板别名,即这会产生编译错误:

float x, y;

my_array a { x, y }; // works
special b { x, y }; // doesn't

我们仍然可以说special&lt;float&gt; b 并感到高兴。但是,假设 T 是一些冗长乏味的类型名称,例如std::vector&lt;std::pair&lt;int, std::string&gt;&gt;::const_iterator。在这种情况下,在这里进行模板参数推导将非常方便。所以,我的问题是:如果我们真的希望 special 是一个等于(在某种意义上my_array&lt;T, 2&gt; 的类型,并且我们真的希望演绎指南(或类似的东西)能够工作,如何有人能克服这个限制吗?

对于一个有点含糊的问题,我提前道歉。


我提出了几个解决方案,但都有严重的缺点。

1) 将special 设为具有相同接口的单独无关类,即

template <typename T>
struct special
{
    T values[2];
};

template <typename T>
special (T, T) -> special<T>;

这种重复看起来很尴尬。此外,而不是编写像

这样的函数
void foo (my_array<T, N>);

我不得不复制它们

void foo (my_array<T, N>);
void foo (special<T>);

或去做

template <typename Array>
void foo (Array);

并且依赖这些类的接口是相同的。我一般不喜欢这种代码(接受任何东西并完全依赖鸭子打字)。它可以通过一些 SFINAE/概念来改进,但这仍然感觉很尴尬。

2) 将special 设为函数,即

template <typename T>
auto special (T x, T y)
{
    return my_array { x, y };
}

这里没有类型重复,但现在我不能声明special类型的变量,因为它是一个函数,而不是一个类型。

3) 保留 special 模板别名,但提供类似 C++17 之前的 make_special 函数:

template <typename T>
auto make_special (T x, T y)
{
    return my_array { x, y };
    // or return special<T> { x, y };
}

它在某种程度上有效。尽管如此,这不是一个演绎指南,并且将使用演绎指南的类与 make_XXX 函数混合在一起听起来令人困惑。

4) 按照@NathanOliver 的建议,使special 继承自my_array

template <typename T>
struct special : my_array<T, 2>
{};

这使我们能够为special 提供单独的推演指南,并且不涉及代码重复。但是,编写类似的函数可能是合理的

void foo (special<int>);

不幸的是,它无法接受my_array&lt;2&gt;。它可以通过提供从my_array&lt;2&gt;special 的转换运算符来解决,但这意味着我们在它们之间进行循环转换,这(以我的经验)是一场噩梦。同样special在使用列表初始化时需要extra curly braces


还有其他方法可以模拟类似的东西吗?

【问题讨论】:

  • 有特殊继承自数组吗?
  • @NathanOliver 我没有考虑继承,因为如果可以的话,我通常不喜欢创建类层次结构。现在我正在考虑它,这实际上可能是最好的选择。谢谢你。为什么不让它成为一个答案?
  • @NathanOliver 我添加了一些关于使用继承的想法。
  • 我越来越相信 C++ 需要区分强类型别名和弱类型别名。
  • 请参阅 Class template argument deduction not working with alias template,其中有来自编辑器的答案,其中引用了通过别名模板使 CTAD 在 C++20 中可行的更改。

标签: c++ templates c++17 template-argument-deduction template-aliases


【解决方案1】:

简单的答案是等到 C++20。到那时,类模板推导很可能会学习如何查看别名模板(以及聚合和继承的构造函数,请参阅P1021)。


除此之外,(1)绝对是一个糟糕的选择。但是在 (2)、(3) 和 (4) 之间进行选择很大程度上取决于您的用例。请注意,对于(4),推导指南也不会在 P1021 之前被继承,因此如果您走继承路线,则需要复制基类的推导指南。

但您还错过了第五个选项。无聊的:

special<float> b { x, y }; // works fine, even in C++11

有时候,这样就够了?

【讨论】:

  • 感谢您指点P1021,我会仔细看看。至于你的建议,我不太明白——它只是放弃了问题而不是解决问题。我的意思是,它肯定是一个选项,默认选项。
  • @lisyarus 我的意思是,目标是构建b 对吗?如果您可以在所有这些变通方法之间进行选择,并且只提供一个模板参数...有时只提供一个模板参数是最简单的。
  • 不幸的是,就我而言,T 是一个冗长乏味的类型,这就是为什么我说我真的想要类型推导这里。对于没有在问题中明确说明这一点,我深表歉意。
  • @lisyarus 是的,这真的取决于您在 2/3 路线和 4 路线之间的使用情况。我不知道我可以明确地说哪个更好。 (3) 可能是人们最容易识别的,因为这是我们在 CTAD 出现之前必须做的事情?
  • @lisyarus typedef T EasyToType 然后使用 special&lt;EasyToType&gt; 呢?
猜你喜欢
  • 1970-01-01
  • 2021-03-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多