如何模拟模板别名的推导指南?

lis*_*rus 5 c++ templates template-aliases template-argument-deduction c++17

考虑以下:

template <typename T, std::size_t N>
struct my_array
{
    T values[N];
};
Run Code Online (Sandbox Code Playgroud)

我们可以为提供扣减指南my_array,例如

template <typename ... Ts>
my_array (Ts ...) -> my_array<std::common_type_t<Ts...>, sizeof...(Ts)>;
Run Code Online (Sandbox Code Playgroud)

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

template <typename T>
using special = my_array<T, 2>;
Run Code Online (Sandbox Code Playgroud)

事实证明,推论指南根本不适用于模板别名,即会产生编译错误:

float x, y;

my_array a { x, y }; // works
special b { x, y }; // doesn't
Run Code Online (Sandbox Code Playgroud)

我们仍然可以说special<float> b并感到高兴。但是,假设这T是一个冗长乏味的类型名称,例如std::vector<std::pair<int, std::string>>::const_iterator。在这种情况下,在此处进行模板参数推导将非常方便。因此,我的问题是:如果我们真的想special成为与(在某种意义上)相等的类型my_array<T, 2>,并且我们真的希望演绎指南(或类似的东西)起作用,那么如何克服这一限制?

对于一个含糊其词的问题,我事先表示歉意。


我提出了两个解决方案,它们都有严重的缺点。

1)special使用相同的接口创建一个独立的无关类,即

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

template <typename T>
special (T, T) -> special<T>;
Run Code Online (Sandbox Code Playgroud)

这个重复看起来很尴尬。此外,与其编写诸如

void foo (my_array<T, N>);
Run Code Online (Sandbox Code Playgroud)

我被迫复制它们

void foo (my_array<T, N>);
void foo (special<T>);
Run Code Online (Sandbox Code Playgroud)

或做

template <typename Array>
void foo (Array);
Run Code Online (Sandbox Code Playgroud)

并依赖于此类的接口是相同的。我通常不喜欢这种代码(接受任何内容并完全依赖于鸭子输入)。可以通过一些SFINAE /概念来改进它,但是仍然感到尴尬。

2)做special一个功能,即

template <typename T>
auto special (T x, T y)
{
    return my_array { x, y };
}
Run Code Online (Sandbox Code Playgroud)

这里没有类型重复,但是现在我不能声明类型的变量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 };
}
Run Code Online (Sandbox Code Playgroud)

它在某种程度上起作用。但是,这不是推导指南,并且将带有推导指南的类混合使用时make_XXX听起来很混乱。

4)根据@NathanOliver的建议,使special继承自my_array

template <typename T>
struct special : my_array<T, 2>
{};
Run Code Online (Sandbox Code Playgroud)

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

void foo (special<int>);
Run Code Online (Sandbox Code Playgroud)

不幸的是无法接受my_array<2>。可以通过提供从my_array<2>到的转换运算符来解决special此问题,但这意味着我们之间要进行循环转换,(以我的经验)这是一场噩梦。使用列表初始化时,还special需要额外的花括号


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

Bar*_*rry 5

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


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

但您还错过了第五种选择。无聊的一个:

special<float> b { x, y }; // works fine, even in C++11
Run Code Online (Sandbox Code Playgroud)

有时候,这样就够了吗?

  • @lisyarus 所以是的,这实际上取决于您在 2/3 路线和 4 路线之间的使用情况。我不知道我能明确地说哪个更好。(3) 可能是人们最容易识别的,因为这就是我们在 CTAD 出现之前必须做的事情? (2认同)