为什么结构不能作为模板非类型参数传递?

Bar*_*icz 11 c++ templates c++11

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

template<int x>
void foo() { cout << x; }
Run Code Online (Sandbox Code Playgroud)

除了int那种情况还有其他选择,我想谈谈这个很好的答案.

现在,有一件事让我感到困惑:结构.考虑:

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

Triple t { 1, 2, 3 };

template<Triple const& t>
class Foo { };
Run Code Online (Sandbox Code Playgroud)

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

Foo<t> f;
Run Code Online (Sandbox Code Playgroud)

什么是这里值得注意的是,t 不能constexpr甚至const,因为这意味着内部联动,这基本上意味着,该行不会编译.我们可以通过声明绕过tconst extern.这本身可能有点奇怪,但真正令我惊讶的是为什么这是不可能的:

Foo<Triple { 1, 2, 3 }> f;
Run Code Online (Sandbox Code Playgroud)

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

error:Triple{1, 2, 3}不是类型的有效模板参数,const Triple&因为它不是左值.

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

小智 16

用户的更新答案:

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 {};
Run Code Online (Sandbox Code Playgroud)

此外,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}>();
}
Run Code Online (Sandbox Code Playgroud)

cppreference

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

请注意,模板参数允许的类文字类型有很多限制。有关更多详细信息,请查看我写的解释文字类 NTTP 的用法和限制的博客文章:文字类作为 C++20 中的非类型模板参数

  • 哦,这真是太神奇了!感谢您通过如此优质的帖子重新审视这个问题。 (2认同)

R. *_*des 13

我认为"因为mikta的痛苦才能正确指定和实施"是我们没有这个的主要原因.当然,很容易让这一点工作,但人们会抱怨使用结构模板参数在其他模板参数所做的所有相同情况下都不起作用(考虑部分特化,或者如何处理operator==).

在我看来,得到整个蛋糕太乱了,只得到一小块就不够令人满意了,可能更令人沮丧.只做这个小小的工作不会给我比以下更多的功能,它具有开箱即用的各种东西(包括部分特化)的额外优势.

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; }
};
Run Code Online (Sandbox Code Playgroud)

  • `meta_triple`是一个很棒的创作.它解决了我非常好的问题. (4认同)

Rei*_*ica 7

您可以定义tconst extern,给它外部链接.然后构造工作:

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

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

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

Foo<t> f;
Run Code Online (Sandbox Code Playgroud)

实例.

您无法将临时值传递给引用模板参数的原因是该参数是引用.如果模板参数是const int&并且您尝试传递,则会收到相同的错误7.例子.

编辑

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

template <Triple t>
struct Foo {};

Foo<Triple {1, 2, 3}> f1;
Foo<Triple {1, 2, 3}> f2;
Run Code Online (Sandbox Code Playgroud)

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

  • 嗯,所以它归结为这样一个事实:该语言对于POD结构没有一个简单的'op ==`,因此两个不同的结构在模板上下文中保持不同,即使它们内部具有完全相同的数据. (3认同)