c++模板声明中的作用域和默认实参:澄清标准

Scope and Default Arguments in Template Declarations in C++ : Clarifying Standardese

本文关键字:实参 默认 标准 作用域 声明 c++      更新时间:2023-10-16

我在阅读c++ 14标准中关于模板的部分,试图提高我对这个主题的理解,偶然发现了这个特殊的规则:

§14.1

同一作用域内的两个不同的声明不能给模板形参提供默认实参。

(例子:

template<class T = int> class X;
template<class T = int> class X { /∗... ∗/ }; // error  

- end示例]

在我看来,"相同作用域"的规范意味着可以在不同的作用域声明模板,而不是在定义它们的地方。

根据这篇多布斯医生的文章

c++定义了五种作用域:函数、函数原型、局部、命名空间和类。

其中,我的理解是:

  • 功能,(我假设函数原型,因为它只将过去的函数扩展到声明)作用域不能包含模板声明
  • 局部作用域属于功能作用域,因此与上述
  • 具有相同的限制
  • 你不能(重新)声明类声明之外的任何成员。
因此,允许在定义的作用域之外声明的潜在奇怪情况(可能使用更改的默认参数,取决于作用域!)似乎完全落在了名称空间作用域的肩上。我做了一些实验: [Coliru]


命令:

g++ -std=c++1z -O2 -Wall -pedantic -pthread main.cpp && ./a.out

代码:

#include <iostream>
namespace math{ 
    template <int I, int J>
    struct Plus{
        constexpr static int Value_ = I + J;
    };
    template <int I, int J = 5>
    struct Minus; // template declaration.
}
// global-scope template declaration?
//template <int I, int J>
//struct math::Minus; // error: does not name a type (if no declaration @ line 9)
                      // error: invalid use of math::Minus w/o argument list
                      //                             (if declaration @ line 9)

namespace math{
    template <int I, int J>
    struct Minus{
        static int value();
    };
    namespace{ // anonymous namespace
        //template <int I, int J = 5>
        //struct Minus; compiles, but is a declaration of another class,
                       // which I assume hides the math scope class
        // error: incomplete type--I assume this means the above 
        // doesn't declare math::Minus, but math::<anon>::Minus
        //constexpr int twominus5= Minus<2>::value(); 
    } // end anonymous namespace
} // end math namespace
//template <int I, int J>
//class math::Minus; // error: invalid use of math::Minus w/o argument list
template <int I, int J>
int math::Minus<I,J>::value(){return I - J;}

int main()
{
    std::cout 
        << math::Minus<5,1>::value() << std::endl
        << math::Minus<0>::value() << std::endl;
}
输出:

4
-5

…声明规则似乎符合我在阅读这一小段标准之前所期望的。显然,我的理解在某个地方是错误的。这是我对c++标准的模板默认参数声明子句的初步阅读,还是我错过了在其本机作用域之外声明模板的一些方法?

当然,像这样的语言的一个奇怪的角落(如果它确实存在)应该谨慎使用,特别是因为它会根据最适用的作用域改变其他地方部分指定的模板的行为(它会导致名称冲突问题吗?如果在模板定义的作用域中有默认参数,那么在没有默认声明的作用域中,一个完全限定的名字(甚至)如何解析呢?

我将使用别名,因为这对每个人来说都不那么模棱两可,但正如我上面所说的:我现在很好奇这是我完全没有故意使用的一个奇怪的语言特性,还是我只是想象的一个非特性。

我不确定我能完全理解你的想法,但我认为这个标准只是使用了过于清晰的措辞。这可能是为了澄清,在不同作用域中的"相同"模板可能具有不同的默认参数。例子:

namespace A
{
    template< int = 42 > struct X;
}
namespace B
{
    template< int = 123 > struct X;
    namespace C
    {
        template< int = 0 > struct X;
    }
}
当然,这些模板不是相同的模板(即使初学者第一眼可能会这样认为),但它们是不同的模板。标准的措辞很可能只是为了强调这一点。 可以使用不同默认值的一个例子是模板别名using:
#include <iostream>
#include <type_traits>
namespace A
{
    template< int I = 42 >
    struct X { static void f() { std::cout << I << std::endl; } };
}
namespace B
{
    template< int I = 0 >
    using X = A::X< I >; 
}
int main()
{
    A::X<>::f();
    B::X<>::f();
    static_assert( std::is_same< B::X<>, A::X<0> >::value, "Oops" );
}
<<p> 生活例子/strong>

这个问题是,它看起来像它匹配你的描述,但有趣的是,虽然B::X<>A::X<0>是相同的类型B::X目前不是相同的模板作为A::X

有一个DR (CWG issue 1286)来修复这个问题。不同的默认参数OTOH是DR中提到的一个问题,所以即使DR将被解决,它也可能不允许不同的默认值。

在同一模板的不同作用域中声明不同默认参数的一个例子是在不同的翻译单元中声明它们:

// x.hpp
template <class T> class X;
// a.cpp
#include "x.hpp"
template <class T = int> class X;
// b.cpp
#include "x.hpp"
template <class T = float> class X;

在这里,我们有两个翻译单元创建了两个作用域,它们都声明了相同的实体(类模板X),并且在每个全局作用域中,我们都有一个新的X声明,带有不同的默认模板参数。