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

How to emulate deduction guides for template aliases?

考虑以下几点:

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<T, 2>有一些非常特殊的含义(但只是含义,接口和实现保持不变),所以我们想给它一个更合适的名字:

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

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

float x, y;

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

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

我提前为一个有点含糊的问题道歉。


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

1) 使 special 成为一个单独的不相关的 class,具有相同的界面,即

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);

和依赖这个class的接口是一样的。我一般不喜欢这种代码(接受任何东西并完全依赖鸭子打字)。可以通过一些SFINAE/concepts来改进,但是这样还是感觉很别扭

2) 使special成为一个函数,即

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

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

3) 保留 special 一个模板别名,但提供一个类似于 pre-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 };
}

它在某种程度上有效。不过,这不是演绎指南,将使用演绎指南的 classes 与此 make_XXX 函数混合使用听起来令人困惑。

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

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

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

这样的函数可能是合理的
void foo (special<int>);

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


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

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


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

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

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

有时候,这就足够了吗?