C++ 我必须将“模板”和“类型名称”关键字放在何处以及为什么?
在模板中,我在哪里以及为什么必须将typename和template放在从属名称上? 到底什么是从属名称 我有以下代码:C++ 我必须将“模板”和“类型名称”关键字放在何处以及为什么?,c++,templates,typename,c++-faq,dependent-name,C++,Templates,Typename,C++ Faq,Dependent Name,在模板中,我在哪里以及为什么必须将typename和template放在从属名称上? 到底什么是从属名称 我有以下代码: template <typename T, typename Tail> // Tail will be a UnionNode too. struct UnionNode : public Tail { // ... template<typename U> struct inUnion { // Q: where t
template <typename T, typename Tail> // Tail will be a UnionNode too.
struct UnionNode : public Tail {
// ...
template<typename U> struct inUnion {
// Q: where to add typename/template here?
typedef Tail::inUnion<U> dummy;
};
template< > struct inUnion<T> {
};
};
template <typename T> // For the last node Tn.
struct UnionNode<T, void> {
// ...
template<typename U> struct inUnion {
char fail[ -2 + (sizeof(U)%2) ]; // Cannot be instantiated for any U
};
template< > struct inUnion<T> {
};
};
我遇到的问题是typedef Tail::inUnion伪行。我相当肯定inUnion是一个依赖名称,VC++完全正确地扼杀了它。
我还知道我应该能够在某个地方添加模板,告诉编译器inUnion是一个模板id。但具体在哪里呢?然后,它是否应该假设inUnion是一个类模板,即inUnion命名一个类型而不是一个函数
typedef typename Tail::inUnion<U> dummy;
然而,我不确定您对inUnion的实现是否正确。如果我理解正确的话,这个类不应该被实例化,因此fail选项卡永远不会失败。也许最好用一个简单的布尔值指示类型是否在并集中
template <typename T, typename TypeList> struct Contains;
template <typename T, typename Head, typename Tail>
struct Contains<T, UnionNode<Head, Tail> >
{
enum { result = Contains<T, Tail>::result };
};
template <typename T, typename Tail>
struct Contains<T, UnionNode<T, Tail> >
{
enum { result = true };
};
template <typename T>
struct Contains<T, void>
{
enum { result = false };
};
附:看一看
PS2:请看,特别是在Andrei Alexandrescu的书中:现代C++设计< /P> < P>见
< >为了分析C++程序,编译器需要知道某些名称是否为类型。以下示例说明:t * f;
这应该如何解析?对于许多语言,编译器不需要知道名称的含义就可以解析代码,并且基本上知道一行代码的作用。然而,C++中,根据T的含义,可以产生很大不同的解释。如果它是一个类型,那么它将是一个指针f的声明。然而,如果它不是一个类型,它将是一个乘法。因此,C++标准在第3/7段:
有些名称表示类型或模板。通常,每当遇到名称时,在继续解析包含该名称的程序之前,必须确定该名称是否表示这些实体之一。确定这一点的过程称为名称查找
如果t引用模板类型参数,编译器将如何找出t::x所指的名称?x可以是一个可以相乘的静态int数据成员,也可以是一个嵌套类或typedef,可以生成一个声明。如果一个名称有这样一个属性,即在知道实际的模板参数之前无法查找它,那么它被称为从属名称,这取决于模板参数
您可能建议等待用户实例化模板:
让我们等到用户实例化该模板,然后再找出t::x*f;的真正含义
这将起作用,并且实际上是标准允许的一种可能的实现方法。这些编译器基本上将模板的文本复制到内部缓冲区中,并且仅当需要实例化时,才解析模板并可能检测定义中的错误。但是可怜的同事们没有打扰模板的用户!对于模板作者所犯的错误,其他实现会选择尽早检查模板,并在实例化之前尽快给出定义中的错误
因此,必须有一种方法告诉编译器某些名称是类型,而某些名称不是类型
typename关键字
答案是:我们决定编译器应该如何解析它。如果t::x是一个依赖名称,那么我们需要通过typename为它添加前缀,以告诉编译器以某种方式解析它。标准第14.6/2节规定:
模板声明或定义中使用的、依赖于模板参数的名称是
假定不命名类型,除非适用的名称查找找到类型名称或名称是限定的
通过关键字typename
有许多名称不需要typename,因为编译器可以通过模板定义中适用的名称查找来确定如何解析构造本身,例如使用T*f;,当T是类型模板参数时。但是对于t::x*f;要成为一个声明,它必须写为typename t::x*f;。如果省略关键字并且名称被视为非类型,但当实例化发现它表示类型时,编译器会发出通常的错误消息。有时,在定义时给出误差:
// t::x is taken as non-type, but as an expression the following misses an
// operator between the two names or a semicolon separating them.
t::x f;
语法只允许在限定名之前使用typename——因此,如果非限定名引用类型,则通常认为它们引用类型是理所当然的
正如介绍性文本所暗示的,对于表示模板的名称也存在类似的问题
模板关键字
还记得上面的初始引用以及标准如何要求对模板进行特殊处理吗?让我们举一个看起来很无辜的例子:
boost::function< int() > f;
这实际上是一个有效的表达式!它使用小于运算符将boost::函数与零int进行比较,然后使用大于运算符将得到的bool与f进行比较。但是,正如您所知,boost::function是一个模板,因此
编译器知道14.2/3:
名称查找后,3.4发现一个名称是一个模板名称,如果这个名称后面跟一个C++11
问题
虽然C++03中关于何时需要typename和template的规则在很大程度上是合理的,但它的公式有一个恼人的缺点
template<typename T>
struct A {
typedef int result_type;
void f() {
// error, "this" is dependent, "template" keyword needed
this->g<float>();
// OK
g<float>();
// error, "A<T>" is dependent, "typename" keyword needed
A<T>::result_type n1;
// OK
result_type n2;
}
template<typename U>
void g();
};
这很令人印象深刻,但我们能做得更好吗?该语言甚至更进一步,要求实现在实例化D::f时再次查找D::result_类型,即使它在定义时已经找到了它的含义。当查找结果不一致或导致歧义时,程序格式错误,必须进行诊断。想象一下如果我们这样定义C会发生什么
template<>
struct C<int> {
typedef bool result_type;
typedef int questionable_type;
};
在C++03中,允许捕获此错误的语言,因为无论您给T的参数是什么,都不可能有一种有效的方法来实例化a::h。在C++11中,该语言现在有一个进一步的检查,为编译器实现此规则提供更多的理由。由于A没有依赖的基类,并且A没有声明成员problem_类型,因此名称A::problem_类型既不是当前实例化的成员,也不是未知专门化的成员。在这种情况下,该代码不可能在实例化时有效编译,因此该语言禁止限定符为当前实例化的名称既不是未知专门化的成员,也不是当前实例化的成员。但是,仍然不需要诊断此冲突
例子和琐事
你可以尝试一下这些知识,看看上面的定义在现实世界的例子中是否对你有意义。答案中重复了这些定义,但不太详细
C++ 11规则使得下列有效C++ 03代码不正确,这不是C++委员会的意图,但可能不一定是固定的< /P>
struct B { void f(); };
struct A : virtual B { void f(); };
template<typename T>
struct C : virtual B, T {
void g() { this->f(); }
};
int main() {
C<A> c; c.g();
}
这段有效的C++03代码将在实例化时将此->f绑定到A::f,一切正常。但是,C++11会立即将其绑定到B::f,并且在实例化时需要进行双重检查,检查查找是否仍然匹配。但是,当实例化C时,将应用并查找A::f。
这是一个相当简短和甜蜜的答案来回答标题问题的一部分。如果你想要一个更详细的答案,解释为什么你必须把它们放在那里,请去。
放置typename关键字的一般规则主要是当您使用模板参数并且希望访问嵌套的typedef或使用alias时,例如:
template<typename T>
struct test {
using type = T; // no typename required
using underlying_type = typename T::type // typename required
};
template<typename T>
struct test {
// typename required
using type = typename std::conditional<true, const T&, T&&>::type;
// no typename required
using integer = std::conditional<true, int, float>::type;
};
template< typename T > void foo( T& x, std::string str, int count )
{
// these names are looked up during the second phase
// when foo is instantiated and the type T is known
x.size(); // dependant name (non-type)
T::instance_count ; // dependant name (non-type)
typename T::iterator i ; // dependant name (type)
// during the first phase,
// T::instance_count is treated as a non-type (this is the default)
// the typename keyword specifies that T::iterator is to be treated as a type.
// these names are looked up during the first phase
std::string::size_type s ; // non-dependant name (type)
std::string::npos ; // non-dependant name (non-type)
str.empty() ; // non-dependant name (non-type)
count ; // non-dependant name (non-type)
}
添加模板限定符的一般规则基本相似,但它们通常涉及模板化成员函数静态或自身模板化的结构/类的其他方面,例如:
template<typename T>
struct test {
using type = T; // no typename required
using underlying_type = typename T::type // typename required
};
template<typename T>
struct test {
// typename required
using type = typename std::conditional<true, const T&, T&&>::type;
// no typename required
using integer = std::conditional<true, int, float>::type;
};
template< typename T > void foo( T& x, std::string str, int count )
{
// these names are looked up during the second phase
// when foo is instantiated and the type T is known
x.size(); // dependant name (non-type)
T::instance_count ; // dependant name (non-type)
typename T::iterator i ; // dependant name (type)
// during the first phase,
// T::instance_count is treated as a non-type (this is the default)
// the typename keyword specifies that T::iterator is to be treated as a type.
// these names are looked up during the first phase
std::string::size_type s ; // non-dependant name (type)
std::string::npos ; // non-dependant name (non-type)
str.empty() ; // non-dependant name (non-type)
count ; // non-dependant name (non-type)
}
给定此结构和函数:
template<typename T>
struct test {
template<typename U>
void get() const {
std::cout << "get\n";
}
};
template<typename T>
void func(const test<T>& t) {
t.get<int>(); // error
}
尝试从函数内部访问t.get将导致错误:
main.cpp:13:11:错误:应在“int”之前使用主表达式
t、 得到;
^
main.cpp:13:11:错误:应为“;”在“int”之前
因此,在此上下文中,您需要预先使用template关键字,并像这样调用它:
t、 模板获取
这样,编译器将正确地解析这个,而不是t.get我把JLBorges的优秀文章逐字逐句地放到cplusplus.com上的一个类似问题上,因为这是我读过的关于这个主题的最简洁的解释 在我们编写的模板中,有两种名称可以使用-依赖名称和非依赖名称。从属名称是依赖于模板参数的名称;无论模板参数是什么,非从属名称都具有相同的含义 例如:
template<typename T>
struct test {
using type = T; // no typename required
using underlying_type = typename T::type // typename required
};
template<typename T>
struct test {
// typename required
using type = typename std::conditional<true, const T&, T&&>::type;
// no typename required
using integer = std::conditional<true, int, float>::type;
};
template< typename T > void foo( T& x, std::string str, int count )
{
// these names are looked up during the second phase
// when foo is instantiated and the type T is known
x.size(); // dependant name (non-type)
T::instance_count ; // dependant name (non-type)
typename T::iterator i ; // dependant name (type)
// during the first phase,
// T::instance_count is treated as a non-type (this is the default)
// the typename keyword specifies that T::iterator is to be treated as a type.
// these names are looked up during the first phase
std::string::size_type s ; // non-dependant name (type)
std::string::npos ; // non-dependant name (non-type)
str.empty() ; // non-dependant name (non-type)
count ; // non-dependant name (non-type)
}
对于模板的每个不同实例化,从属名称所指的内容可能有所不同。因此,C++模板需要进行两阶段名称查找。当在任何实例化发生之前对模板进行初始解析时,编译器将查找非依赖名称。当模板的特定实例化发生时,此时模板参数已知,编译器将查找相关名称
在第一阶段,解析器需要知道从属名称是类型名称还是非类型名称。默认情况下,从属名称假定为非类型的名称。从属名称之前的typename关键字指定它是类型的名称
总结
仅在模板声明和定义中使用关键字typename,前提是您有一个引用类型并依赖于模板参数的限定名称。C++20又名C++2a
如本文所述,C++20/C++2a进一步放宽了对typename关键字的要求。特别是,typename现在可以在所有这些地方省略,在这些地方,语法上只有一个类型是合法的。因此,如果一个未知的令牌必须是一个类型,C++20实际上会将其视为一个类型。但是,为了向后兼容,仍然可以使用typename
特别是,大多数using和typedef声明现在可以不用typename编写。在方法返回类型(包括尾部返回类型)的声明中,在方法和lambda参数的声明中,以及在static_cast、const_cast、dynamic_cast和reinterpret_cast的类型参数中,也可以省略typename
一个值得注意的例外是,typename仍然是必需的,它出现在用户或库定义模板的实例化参数列表中:即使该特定参数被声明为类型,typename关键字仍然是必需的。因此,在C++20中,static_castarg是合法的,但是如果A是从属作用域并且我的_template_类需要类型,那么我的_template_classarg的格式是错误的
举几个例子:
class A { public: typedef int type; static const int val { 1 }; };
class B { public: typedef float type; static const int val { 2 }; };
template<typename T> class C {};
template<int I> class D {};
template<typename T> class X {
T::type v; // OK
T::type f(T::type arg) { return arg; } // OK
T::type g(double arg) { return static_cast<T::type>(arg); } // OK
// C<T::type> c1; // error
D<T::val> d; // OK (as has always been)
C<typename T::type> c2; // OK (old style)
typedef T::type mytype; // OK
using mytypeagain = T::type; // OK
C<mytype> c3; // OK (via typedef / using)
};
X<A> xa;
X<B> xb;
Dependent name是一个依赖于模板参数的名称,我们需要指示编译器在实际输入模板类/函数之前正确编译模板类/函数 typename->告诉编译器依赖名称是实际类型
template <class T>
struct DependentType
{
typename T::type a;
using Type=typename T::type;
};
模板->告诉编译器依赖名称是模板函数/类
template <class T>
struct DependentTemplate
{
// template function
template <class U>
static void func() {}
// template class
template <class U>
struct ClassName{};
};
template <class T1, class T2>
void foo()
{
// 3 ways to call a dependent template function
DependentTemplate<T1>::template func<T2>();
DependentTemplate<T1>().template func<T2>();
(new DependentTemplate<T1>())->template func<T2>();
// You need both typename and template to reference a dependent template class
typename DependentTemplate<T1>::template ClassName<T2> obj;
using Type=typename DependentTemplate<T1>::template ClassName<T2>;
}
政治敏感性,可移植性。我问了你的实际问题,模板/字体名放在哪里?将最后一个问题和代码放在开头,并水平缩短代码以适应1024x屏幕,这样会更好地脱颖而出。从标题中删除从属名称,因为似乎大多数对typename和template感到好奇的人都不知道从属名称是什么。这样应该不会让他们太困惑了。@MSalters:boost非常便携。我想说,只有政治才是推动经济增长常常被忽视的一般原因。我知道的唯一好的原因是构建时间的增加。否则,这一切都是因为重新发明轮子而损失了数千美元。现在在我看来,char失败了[-sizeofU];//无法为任何U实例化将不起作用,因为-sizeofU仍然始终为正,因此它可能仍然适用于某些或a
例如,如果您试图用U==int调用Union::operator=U,那么ll U.inUnion将被实例化。它调用私有setU,inUnion*=0。而result=true/false的工作是我需要boost::enable_if<>,这与我们当前的OSX工具链不兼容。但是,单独的模板仍然是一个好主意;线这将实例化Tail。但不是在学校里。当它需要它的完整定义时,它会被实例化。例如,如果使用sizeof,或者使用::foo访问成员,就会发生这种情况@MSalters无论如何,您还有另一个问题:-sizeofU从不为负:因为size\u t是无符号整数类型。你会得到一些非常高的数字。您可能想执行sizeofU>=1-1:1或类似:。。。那么字符f[sizeofU>=1?-1:1]或-sizeofU永远无效。我很久以前就看过了,但今天早上我又找到了那一段:14.6/7。不要求它拒绝,但它可以这样做。但是,如果你只是把模板声明放进去,就没问题了。这个答案是从我之前删除的FAQ条目中复制出来的,因为我发现我应该更好地使用现有的类似问题,而不是为了回答它们而编造新的伪问题。感谢go to,他编辑了最后一部分中禁止使用typename/template的案例的想法。你能帮助我什么时候使用这种语法吗?这个->模板f;我得到了这个错误“template”,因为消歧器只允许在模板中使用,但没有模板关键字,效果很好。我今天问了一个类似的问题,很快就被标记为重复:。我奉命重新提出这个问题,而不是提出一个新问题。我必须说我不同意它们是重复的,但我是谁,对吗?那么,有什么理由在语法不允许除类型名以外的其他解释的情况下强制使用typename吗?@Pablo你没有遗漏任何东西。但仍然需要编写消歧代码,即使整行不再含糊不清。@L.F.请创建一个新的C++20答案,正如我在C++ 11FYI中所做的那样,这里的答案是:这个答案中的大部分代码没有在各种编译器上编译。@ AdAMrACKISE假设C++规范自2013个日期以来没有改变,我写了这个答案,然后尝试了代码,但是没有实现这个C++ 11 +特性。
struct X { typedef int foo; }; /* (C) --> */ f_tmpl<X> ();
struct Y { static int const foo = 123; }; /* (D) --> */ f_tmpl<Y> ();
template<class T> void g_tmpl () {
SomeTrait<T>::type foo; // (E), ill-formed
SomeTrait<T>::NestedTrait<int>::type bar; // (F), ill-formed
foo.data<int> (); // (G), ill-formed
}
template<class T> void g_tmpl () {
typename SomeTrait<T>::type foo; // (G), legal
typename SomeTrait<T>::template NestedTrait<int>::type bar; // (H), legal
foo.template data<int> (); // (I), legal
}
namespace N {
template<class T>
struct X { };
}
N:: X<int> a; // ... legal
typename N::template X<int> b; // (K), legal
typename template X<int> c; // (L), ill-formed
// .------- the base-specifier-list
template<class T> // v
struct Derived : typename SomeTrait<T>::type /* <- ill-formed */ {
...
};
struct Base {
template<class T>
struct type { };
};
struct Derived : Base {
using Base::template type; // ill-formed
using Base::type; // legal
};
template< typename T > void foo( T& x, std::string str, int count )
{
// these names are looked up during the second phase
// when foo is instantiated and the type T is known
x.size(); // dependant name (non-type)
T::instance_count ; // dependant name (non-type)
typename T::iterator i ; // dependant name (type)
// during the first phase,
// T::instance_count is treated as a non-type (this is the default)
// the typename keyword specifies that T::iterator is to be treated as a type.
// these names are looked up during the first phase
std::string::size_type s ; // non-dependant name (type)
std::string::npos ; // non-dependant name (non-type)
str.empty() ; // non-dependant name (non-type)
count ; // non-dependant name (non-type)
}
class A { public: typedef int type; static const int val { 1 }; };
class B { public: typedef float type; static const int val { 2 }; };
template<typename T> class C {};
template<int I> class D {};
template<typename T> class X {
T::type v; // OK
T::type f(T::type arg) { return arg; } // OK
T::type g(double arg) { return static_cast<T::type>(arg); } // OK
// C<T::type> c1; // error
D<T::val> d; // OK (as has always been)
C<typename T::type> c2; // OK (old style)
typedef T::type mytype; // OK
using mytypeagain = T::type; // OK
C<mytype> c3; // OK (via typedef / using)
};
X<A> xa;
X<B> xb;
template <class T>
struct DependentType
{
typename T::type a;
using Type=typename T::type;
};
template <class T>
struct DependentTemplate
{
// template function
template <class U>
static void func() {}
// template class
template <class U>
struct ClassName{};
};
template <class T1, class T2>
void foo()
{
// 3 ways to call a dependent template function
DependentTemplate<T1>::template func<T2>();
DependentTemplate<T1>().template func<T2>();
(new DependentTemplate<T1>())->template func<T2>();
// You need both typename and template to reference a dependent template class
typename DependentTemplate<T1>::template ClassName<T2> obj;
using Type=typename DependentTemplate<T1>::template ClassName<T2>;
}