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

时间:2018-05-02 13:42:38

标签: c++ class templates alias c++17

似乎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;
}

这对我来说似乎是出乎意料的行为。是否有任何变通方法仍然允许&#34;&lt;&gt;&#34;被丢弃? (我知道可以使用不同的名称创建单独的typedef,例如:使用MyAlias2 = MyStruct&lt;&gt;,但我想要相同的名称。我也知道定义可以欺骗它,例如#define MyAlias MyStruct,但是假设只会是最后的手段。)

3 个答案:

答案 0 :(得分:7)

  

这对我来说似乎是出乎意料的行为。是否有任何变通方法仍然允许“&lt;&gt;”被放弃了吗?

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

它不适用于别名模板的上下文(如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?

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

答案 1 :(得分:3)

  

是否有任何变通方法仍允许“&lt;&gt;”被放弃了吗?

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

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

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

  

这对我来说似乎是出乎意料的行为。

Class template argument deduction,它启用您尝试使用的功能,似乎需要一个真实类模板的名称,而不是模板别名的名称。编译器基本上做的就是转换

MyStruct obj;

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

auto obj = f();

但是,对于别名,你可以这样做:

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

如果只是将MyAlias替换为MyStruct,上述转换完全会错过“+ 1”,这意味着这个问题没有一个简单的解决方案 - 但此时标准中没有任何内容这种情况得到了处理,因此可以理解它无法编译。

答案 2 :(得分: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;
}