请考虑以下内容:
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
。在这种情况下,在此处进行模板参数推导将非常方便。因此,我的问题是:如果我们真的希望special
与my_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的建议,使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。
还有其他方法可以模仿类似的东西吗?
答案 0 :(得分:2)
简单的答案是等到C ++ 20。届时很有可能将学习如何通过别名模板(以及聚合和继承的构造函数,请参见P1021)来学习类模板推导。
除此之外,(1)绝对是一个错误的选择。但是,在(2),(3)和(4)之间进行选择主要取决于您的用例。请注意,对于(4),推导指南直到P1021都不会被继承,因此,如果走继承路线,则需要复制基类的推导指南。
但是您还错过了第五种选择。无聊的人:
special<float> b { x, y }; // works fine, even in C++11
有时候,够了吗?