【问题标题】:Why can't a struct be passed as value as template non-type parameter?为什么不能将结构作为模板非类型参数的值传递?
【发布时间】:2013-04-09 08:08:32
【问题描述】:

非类型模板参数显然是不是类型的,例如:

template<int x>
void foo() { cout << x; }

在这种情况下,除了int,还有其他选项,我想参考this great answer

现在,有一件事让我很头疼:结构。考虑:

struct Triple { int x, y, z; };

Triple t { 1, 2, 3 };

template<Triple const& t>
class Foo { };

现在,使用普通的非类型引用语义,我们可以这样写:

Foo<t> f;

这里值得注意的是t不能constexpr甚至const,因为这意味着内部链接,这基本上意味着该行不会编译。我们可以通过将t 声明为const extern 来绕过它。这本身可能有点奇怪,但真正让我想知道的是为什么这是不可能的:

Foo<Triple { 1, 2, 3 }> f;

我们从编译器中得到一个非常不错的错误:

错误:Triple{1, 2, 3} 不是 const Triple&amp; 类型的有效模板参数,因为它不是左值。

我们不能在模板中按值指定Triple,因为这是不允许的。但是,我无法理解那一小行代码的真正问题。不允许使用结构作为值参数的原因是什么。如果我可以使用三个ints,为什么不使用三个整数的结构?如果它只有微不足道的特殊成员,那么它在处理上应该与只有三个变量没有什么不同。

【问题讨论】:

  • 该标准只允许 int 和我认为的成员指针。您甚至不能将 double 或 float 作为非类型参数。甚至 const char* 都非常有用(并且在 VC6 上令人惊讶地工作)。从一些答案来看,不确定 C++11 是否有任何变化。
  • @Pete const char* 存在写在不同地方的相同文字之间不等式的问题,对于 double/float 也一样。此外,您跳过了参考。我的问题不是关于这是否可能(显然不是),而是为什么不是
  • 我猜可能有太多的可能性来为标准磨练任何东西。如果它仅限于 int,则更容易指定它。也许它会在 50 年后出现在下一个 C++ 标准中。
  • @Pete 您是否使用a == b 测试两个浮点变量是否相等?
  • 出于同样的原因没有隐式定义operator==?

标签: c++ templates c++11


【解决方案1】:

用户的更新答案:

C++20 增加了对类文字(带有constexpr 构造函数的类)非类型模板参数的支持,这将允许原始问题中的示例工作,前提是模板参数被值接受:

template<Triple t> // Note: accepts t by value
class Foo { };

// Works with unnamed instantiation of Triple.
Foo<Triple { 1, 2, 3 }> f1 {};

// Also works if provided from a constexpr variable.
constexpr Triple t { 1, 2, 3 };
Foo<t> f2 {};

另外,整个程序中Triple { 1, 2, 3 }的所有模板参数实例都将引用相同静态存储时长对象:

template<Triple t1, Triple t2>
void Func() {
    assert(&t1 == &t2); // Passes.
}

constexpr Triple t { 1, 2, 3 };

int main()
{
    Func<t, Triple {1, 2, 3}>();
}

来自cppreference

命名类类型 T 的非类型模板形参的标识符表示类型为 const T 的静态存储持续时间对象,称为模板形参对象,其值是对应的模板实参转换为模板参数的类型。 程序中所有此类具有相同值的相同类型的模板参数表示相同的模板参数对象。

请注意,模板参数允许的类文字类型有很多限制。有关更多详细信息,请查看我写的这篇博客文章,解释文字类 NTTP 的用法和限制:Literal Classes as Non-type Template Parameters in C++20

【讨论】:

  • 哦,这太神奇了!感谢您以如此高质量的帖子重新审视这个问题。
【解决方案2】:

让这部分工作很容易,但是人们会抱怨在其他模板参数所做的所有相同情况下使用结构模板参数如何工作(考虑部分专业化,或者如何处理@987654321 @)。

在我看来,整块蛋糕太乱了,只吃一小块不够令人满意,可能更令人沮丧。只做这点小事不会给我更多的力量,比如下面的东西,它具有开箱即用的各种东西(包括部分专业化)的额外优势。

template <int X, int Y, int Z>
struct meta_triple {
    // static value getters
    static constexpr auto x = X;
    static constexpr auto y = Y;
    static constexpr auto z = Z;
    // implicit conversion to Triple 
    constexpr operator Triple() const { return { X, Y, Z }; }
    // function call operator so one can force the conversion to Triple with
    // meta_triple<1,2,3>()()
    constexpr Triple operator()() const { return *this; }
};

【讨论】:

  • meta_triple 是一个很棒的创作。它很好地解决了我遇到的问题。
  • 你能做一个函数,将Triple转换成meta_triple吗?
  • @Eric 不,这是单向路。
【解决方案3】:

您可以将t 定义为const extern,为其提供外部链接。然后构造工作:

struct Triple { int x, y, z; };

const extern Triple t { 1, 2, 3 };

template<Triple const& t>
class Foo { };

Foo<t> f;

Live example.

您不能将临时参数传递给引用模板参数的原因是该参数是一个引用。如果模板参数是 const int&amp; 并且您试图通过7Example.

编辑

三个ints 和一个包含三个ints 的结构之间的区别在于,所有int 类型的字面量实际上都是相同的值(7 的所有出现次数只有七个),而每个构造函数调用到一个结构在概念上创建一个新实例。举个假设的例子:

template <Triple t>
struct Foo {};

Foo<Triple {1, 2, 3}> f1;
Foo<Triple {1, 2, 3}> f2;

我认为将这两个构造函数调用“匹配”到同一个模板实例化中会引入额外的复杂性。

【讨论】:

  • Hm,所以归结为该语言没有用于 POD 结构的琐碎的op==,因此两个不同的结构在模板上下文中保持不同,即使它们完全具有里面的数据一样。
猜你喜欢
  • 2019-04-24
  • 2019-11-16
  • 2016-10-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-11-04
相关资源
最近更新 更多