【问题标题】:C++17 alias template with template class default arguments具有模板类默认参数的 C++17 别名模板
【发布时间】:2018-10-12 15:38:47
【问题描述】:

当所有参数都有默认值时,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,但假设会只能作为最后的手段。)

【问题讨论】:

  • Afaik,唯一的方法是你提到的宏。现在的问题是,我多么迫切地希望代表将其发布为答案?嗯..... ;)
  • 实际上,我也不知道定义是如何工作的godbolt
  • @iPherian 当前版本 19.10 的 MSVC 用于 Godbolt doesn't support template argument deduction

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


【解决方案1】:

这对我来说似乎是出乎意料的行为。是否有任何解决方法仍然允许删除“”?

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

它不适用于别名模板的上下文(如 OP 中所示)。并且不适用于函数模板推演的上下文。

除非有人提议改变这一点,否则解决方法就是写MyAlias&lt;&gt;


有一个推广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?

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

【讨论】:

  • 您的示例似乎很明确。 “tuple_int t(4, 2);”直觉上会推断出 tuple_int(因为 4 是一个 int),它是一个 std::tuple。不过我可能遗漏了一些东西。
  • @Mark Blegh,意在添加char 案例。固定的。无论如何,我同意有一个直观的、明确的、期望的结果。困难在于提出实现目标的过程 - 不过这是一个有趣的问题,如果您对如何解决它有好的想法,您可以suggest it
【解决方案2】:

是否有任何解决方法仍然允许删除“”?

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

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”,这意味着这个问题没有简单的解决方案 - 但此时在标准中没有处理这种情况, 所以它不会编译是可以理解的。

【讨论】:

  • 感谢另一个解决方法。与#define 一样,这种方法也有利有弊。我希望类模板参数推导能像函数的参数推导一样工作(在我看来,这应该使别名同样简单)。
【解决方案3】:

我不推荐这种方法,但我找到了一种即使您没有 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;
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-02-04
    • 1970-01-01
    • 1970-01-01
    • 2023-01-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多