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

时间:2019-01-13 20:44:57

标签: c++ templates c++17 template-deduction 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>;

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

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。在这种情况下,在此处进行模板参数推导将非常方便。因此,我的问题是:如果我们真的希望specialmy_array<T, 2>相等(在某种意义上说 ),我们真的想要推论指南(或类似的推论)工作,如何克服这一局限性?

我事先为一个模棱两可的问题道歉。


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

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的建议,使specialmy_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


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

1 个答案:

答案 0 :(得分:2)

简单的答案是等到C ++ 20。届时很有可能将学习如何通过别名模板(以及聚合和继承的构造函数,请参见P1021)来学习类模板推导。


除此之外,(1)绝对是一个错误的选择。但是,在(2),(3)和(4)之间进行选择主要取决于您的用例。请注意,对于(4),推导指南直到P1021都不会被继承,因此,如果走继承路线,则需要复制基类的推导指南。

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

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

有时候,够了吗?