如何模拟模板别名的推导指南?
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
有时候,这就足够了吗?
考虑以下几点:
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
有时候,这就足够了吗?