具有模板类默认参数的 C++17 别名模板

C++17 alias template with template class default arguments

本文关键字:C++17 别名 参数 默认      更新时间:2023-10-16

似乎C++17 添加了在所有参数都有默认值时删除模板类上的"<>"的功能(就像我们已经能够长时间使用函数一样(,例如:

template<int LENGTH = 1>
struct MyStruct{ int arr[LENGTH]; };
int main()
{
MyStruct<2> a;
MyStruct<> b; // old way to use defaults
MyStruct c; // new way to use defaults
return 0;
}

但是,在使用别名模板时,该功能似乎不再有效,例如:

template<int LENGTH = 1>
struct MyStruct{ int arr[LENGTH]; };
template<int LENGTH = 1>
using MyAlias = MyStruct<LENGTH>;
int main()
{
MyAlias<2> a;
MyAlias<> b; // old way still works
MyAlias c; // new way doesn't compile:
// gcc 7.3: missing template arguments before 'c'
// clang 6.0.0: declaration of variable 'c' with deduced type 'MyAlias' requires an initializer
return 0;
}

这对我来说似乎是意想不到的行为。是否有任何解决方法仍允许删除"<>"?(我知道可以用不同的名称创建一个单独的typedef,例如:使用MyAlias2 = MyStruct<>,但我想要相同的确切名称。我也知道定义可以欺骗它,例如 #define MyAlias MyStruct,但假设这只是最后的手段。

这对我来说似乎是意想不到的行为。是否有任何解决方法仍允许删除"<>"?

这是预期行为。好吧,取决于你对我的期望。类模板参数推导仅适用于使用未指定任何模板参数的主类模板名称的上下文,并且仅适用于创建对象的上下文。

它不适用于别名模板的上下文(如 OP 所示(。它不适用于函数模板推导的上下文。

除非有人提议改变这一点,否则解决方法是只写MyAlias<>


有一个建议来概括using声明,以便假设您可以编写using MyAlias = MyStruct;并将其作为别名模板。在这种情况下,允许MyAlias c;似乎是合理的,因为MyAlias直接命名类模板。

但一般问题更复杂,因为别名模板可以执行诸如重新排序类型或添加新类型之类的操作。您必须回答以下问题:

template <typename T> using tuple_int = std::tuple<T, int>;
tuple_int t(4, 2);   // would this work? how?
tuple_int u(4, '2'); // what about this?

我不是说没有答案。我只是说这不是一件小事。

是否有任何解决方法仍允许删除"<>"?

可能的解决方法可能是透明继承:

template<int LENGTH = 1>
struct MyStruct{ int arr[LENGTH]; };
template<int LENGTH = 1>
struct MyAlias : MyStruct<LENGTH> { };
int main()
{
MyAlias<2> a;
MyAlias<> b;
MyAlias c;
return 0;
}

然而,一个(可能(危险的副作用是基类没有虚拟析构函数,如果多态使用,可能会导致内存泄漏。

这对我来说似乎是意想不到的行为。

类模板参数推导(启用您尝试使用的功能(似乎需要实际类模板的名称,而不是模板别名的名称。编译器基本上所做的类似于转换

MyStruct obj;

template <int LENGTH=1>
MyStruct<LENGTH> f() { return MyStruct<Length>{ }; }
auto obj = f();

但是,对于别名,您可以执行以下操作:

template <int LENGTH = 1>
using MyAlias = MyStruct<LENGTH + 1>;

如果上面的转换只是用MyStruct替换名称MyAlias,则完全会错过"+ 1",这意味着这个问题没有微不足道的解决方案 - 但到这个时候,标准中没有任何地方处理这种情况,所以可以理解它不会编译。

我不推荐这种方法,但我找到了一种即使您没有 C++17 也能工作的方法。如果您只想进行简单的文本替换并且您有 C++17,您可以使用宏(如问题中所述(。否则,如果您不介意略有不同的语法,则可以这样做:

template<int LENGTH = 1>
struct MyStruct{ int arr[LENGTH]; };
#define MyAlias(...) MyStruct<__VA_ARGS__>
using MyAlias = MyStruct<>;
int main()
{
MyAlias(2) a; // MyAlias<2>
MyAlias() b; // MyAlias<>
MyAlias c;
}

与其他方法相比,有几个优点:

  • 你不需要C++17
  • 您不必重新指定默认值 (DRY 原则(
  • 您不需要任何额外的类、函数等。
  • 它可以与别名模板专用化的解决方法结合使用

例如,假设您要创建一个类来模拟各种大小的浮点数。如果大小与内置类型匹配,则希望直接使用它来提高效率,否则使用类。此外,您希望大小默认为平台上最有效的大小,以减少详细程度。

理想情况下,我希望能够使用别名模板,例如:

template<int BITS>
struct BasicReal final
{
constexpr BasicReal(const double){};
};
template<int BITS = 64>
using Real = BasicReal<BITS>;
template<> // Alias template specialization if it worked
using Real<64> = double;
template<> // Alias template specialization if it worked
using Real<32> = float;
int main()
{
Real r = 1.2; // Alias template argument deduction if it worked
Real<16> r16 = 1.2;
Real<32> r32 = 1.2;
Real<64> r64 = 1.2;
return r;
}

但是,就目前而言,我可以使用以下解决方法(即使在 C++11 中(:

template<int BITS>
struct BasicReal final
{
constexpr BasicReal(const double){};
};
template<int BITS = 64>
struct RealAlias
{
using Type = BasicReal<BITS>;
};
template<>
struct RealAlias<64>
{
using Type = double;
};
template<>
struct RealAlias<32>
{
using Type = float;
};
#define Real(...) RealAlias<__VA_ARGS__>::Type
using Real = RealAlias<>::Type;
int main()
{
Real r = 1.2;
Real(16) r16 = 1.2;
Real(32) r32 = 1.2;
Real(64) r64 = 1.2;
return r;
}