我注意到gcc 5.0拒绝以下代码,而clang 3.6接受它.
template<int n>
struct I
{
typedef int Type;
};
template<typename T>
struct A
{
typedef I<sizeof(sizeof(T))>::Type Type;
};
Run Code Online (Sandbox Code Playgroud)
两个编译器似乎在sizeof(sizeof(T))类型依赖或依赖于值的表达式上有所不同.如果表达式是依赖的,那么它I<sizeof(sizeof(T))>是一个依赖类型,这意味着typename应该是必需的.
这由C++ 11标准中的以下措辞涵盖:
[temp.dep.type]/8
如果是,则类型依赖于
- 一个simple-template-id,其中模板名称是模板参数或任何模板参数是依赖类型或依赖于类型或依赖于值的表达式
[temp.dep.expr/4
以下表单的表达式从不依赖于类型(因为表达式的类型不能依赖):
Run Code Online (Sandbox Code Playgroud)sizeof unary-expression sizeof ( type-id )[temp.dep.constexpr]/2
如果unary-expression或expression是typedependent或type-id是依赖的,则以下形式的表达式是值依赖的:
Run Code Online (Sandbox Code Playgroud)sizeof unary-expression sizeof ( type-id )
我的解释是,sizeof(T)永远不能依赖于类型,意味着sizeof(sizeof(T))永远不能依赖于类型或依赖于价值.
这是gcc中的错误吗?
gcc 5.0和clang 3.6都需要typename以下示例中的关键字:
template<int n>
struct I
{
typedef int Type;
};
template<typename T>
struct A
{
int m;
void f()
{
typedef typename I<sizeof m>::Type Type; // typename required
}
};
Run Code Online (Sandbox Code Playgroud)
这由C++ 11标准中的以下措辞涵盖:
[temp.dep.type]/8
如果是,则类型依赖于
- 一个simple-template-id,其中模板名称是模板参数或任何模板参数是依赖类型或依赖于类型或依赖于值的表达式
所以I<sizeof m>依赖于依赖于sizeof m价值.
[temp.dep.expr/4
以下表单的表达式从不依赖于类型(因为表达式的类型不能依赖):
Run Code Online (Sandbox Code Playgroud)sizeof unary-expression[temp.dep.constexpr]/2
如果unary-expression或expression是typedependent或type-id是依赖的,则以下形式的表达式是值依赖的:
Run Code Online (Sandbox Code Playgroud)sizeof unary-expression
所以sizeof m只依赖m于依赖.
[expr.prim.general]/8
在非静态成员函数的定义中,将指定非静态成员的标识符转换为类成员访问表达式
所以m是一个类的成员访问表达构件.
[temp.dep.type/4
名称是当前实例化的成员(如果是)
- 一个id-expression表示类成员访问表达式(5.2.5)中的成员,对象表达式的类型是当前实例化,而id-expression在查找时(3.4.5),至少指的是当前实例化的一个成员或其非依赖基类.
所以它似乎m是当前实例化的成员.
[temp.dep.type]/5
如果是,则名称是未知专业化的成员
一个id-expression,表示类成员访问表达式(5.2.5)中的成员
对象表达式的类型是当前实例化,当前实例化具有至少一个从属基类,并且id-expression的名称查找未找到当前实例化的成员或其非依赖基类; 要么 …
我想知道为什么在STL中的许多模板算法中,参数不是通过引用传递的,而是通过值传递的.以下是<iterator>标题中的示例:
template<class InputIterator>
typename iterator_traits<InputIterator>::difference_type
distance (InputIterator first, InputIterator last);
Run Code Online (Sandbox Code Playgroud)
当我将两个迭代器传递给此函数时,它们将被复制.我天真的想法是,最好通过const-reference传递这些迭代器,以避免复制迭代器对象:
template<class InputIterator>
typename iterator_traits<InputIterator>::difference_type
distance (const InputIterator &first, const InputIterator &last);
Run Code Online (Sandbox Code Playgroud)
可以说迭代器通常是非常小的对象,复制它们并不昂贵.但即便如此:便宜的复制将比没有复制更昂贵.
那么在STL版本中,迭代器是按值传递的原因是什么?
谢谢!
clang和gcc都接受以下代码并选择A::operator B*.
struct B
{
};
struct A : B
{
operator A*();
operator B*();
};
A a;
void* x = a;
Run Code Online (Sandbox Code Playgroud)
我对标准的阅读 - 特别是下面用粗体突出显示的句子 - 表明这种转换应该是模棱两可的.
这两个A::operator A*和A::operator B*是重载,因为候选人A*和B*都转换为void*通过一个标准的转换.因为隐含的对象参数A&是唯一的参数,所以只考虑从隐含的对象参数转换为隐含的对象参数的转换序列 - 忽略转换函数产生的类型.在这两种情况下,隐含的对象参数都是初始化表达式的类型A,而隐含的对象参数是A&.如果两个转换序列相同,则无法区分两个候选者.
8.5初始值设定项[dcl.init]
初始化器的语义如下.的目标类型是对象或引用的类型被初始化和源类型是初始化表达式的类型.
- 如果目标类型是[ reference/array/class ...] [已删除的详细信息不适用于此方案]
- 否则,如果源类型是(可能是cv限定的)类类型,则考虑转换函数. 列举了适用的转换函数(13.3.1.5),并通过重载决策(13.3)选择最佳函数.调用如此选择的用户定义转换以将初始化表达式转换为正在初始化的对象.如果转换不能完成或不明确,则初始化是错误的.
13.3.1.5通过转换函数初始化[over.match.conv]
在8.5中指定的条件下,作为非类型对象初始化的一部分,可以调用转换函数将类类型的初始化表达式转换为要初始化的对象的类型.重载分辨率用于选择要调用的转换函数.假设"cv1 T"是要初始化的对象的类型,并且"cv S"是初始化表达式的类型,其中S是类类型,候选函数选择如下:
- 考虑S及其基类的转换函数.那些未隐藏在S和yield类型T中的非显式转换函数或可通过标准转换序列(13.3.3.1.1)转换为类型T的类型是候选函数.对于直接初始化,那些未隐藏在S和yield类型T中的显式转换函数或者可以通过限定转换(4.4)转换为类型T的类型也是候选函数.返回cv限定类型的转换函数被认为是为此选择候选函数的过程产生该类型的cv非限定版本.返回"引用cv2 X"的转换函数返回lvalues或xvalues,具体取决于引用类型,类型为"cv2 X",因此被认为是为此选择候选函数的过程产生X.
参数列表有一个参数,它是初始化表达式.[注意:此参数将与转换函数的隐式对象参数进行比较. …
在测试是否typename需要clang 时,我遇到了这种奇怪的行为.clang和gcc都接受此代码,而msvc拒绝它.
template<class T1>
struct A
{
template<class T2>
struct B
{
static B f;
static typename A<T2>::template B<T1> g;
};
};
template<class T1>
template<class T2>
typename A<T2>::template B<T1> // ok, typename/template required
A<T1>::B<T2>::g;
template<class T1>
template<class T2>
A<T1>::B<T2> // clang/gcc accept, msvc rejects missing typename
A<T1>::B<T2>::f;
Run Code Online (Sandbox Code Playgroud)
通常,应编写qualified-id A<T1>::B<T2>(其中A<T1>是依赖名称)typename A<T1>::template B<T2>.gcc/clang的行为是否不正确,或者在这种特殊情况下是否存在一般规则(引用如下)的例外情况?
可以认为它A<T1>不是依赖名称,或者B<T2>指的是当前实例化的成员.但是,在解析类型说明符时,不可能知道当前的实例化A<T1>.要求实现猜测A<T1>当前实例化似乎是有问题的.
14.6名称解析[temp.res]
假定模板声明或定义中使用的名称以及依赖于模板参数的名称不会命名类型,除非适用的名称查找找到类型名称或名称由关键字typename限定.
14.2模板特化名称[temp.names]
当成员模板特化的名称出现在postfix-expression 之后
.或之后,或者出现在->qualified-id中的嵌套名称说明符之后,以及postfix-expression的对象或指针表达式或者嵌套名称说明符中的qualified-id依赖于模板参数(14.6.2)但不引用当前实例化的成员(14.6.2.1),成员模板名称必须以关键字模板为前缀.否则,假定该名称命名非模板.
为了进一步调查clang在这里做了什么,我也尝试了这个:
template<class T1>
struct …Run Code Online (Sandbox Code Playgroud) 使用以下代码,clang 3.0给出error: lookup of 'N' in member access expression is ambiguous,而clang 3.4和gcc 4.8都接受代码而没有错误.
struct B
{
struct M
{
void f()
{
}
};
};
namespace N
{
struct M
{
void f()
{
}
};
}
template<typename>
struct A : N::M, B::M
{
typedef B N;
};
struct D : A<int>
{
A<int> m;
void f()
{
m.N::M::f(); // found class-name 'A<int>::N' (unambiguous)
}
};
template<typename T>
struct C : A<T>
{
A<T> m;
void f()
{ …Run Code Online (Sandbox Code Playgroud) clang和gcc都拒绝这个代码:
template<int i>
struct ambiguous
{
static const int value = i;
};
namespace N
{
template<int i>
void ambiguous();
int i = ambiguous<3>::value; // finds the function template name
}
Run Code Online (Sandbox Code Playgroud)
但是,他们都接受以下代码:
struct ambiguous
{
static const int value = 0;
};
namespace N
{
void ambiguous();
int i = ambiguous::value;
}
Run Code Online (Sandbox Code Playgroud)
该标准表示名称查找前面的名称::"仅考虑其专业化类型的名称空间,类型和模板".clang和gcc是否正确拒绝此代码?如果是这样,我错过了什么?
来自C++工作草案标准n3337
3.4.3限定名称查找[basic.lookup.qual]
在将:: scope resolution运算符(5.1)应用于表示其类,名称空间或枚举的嵌套名称说明符之后,可以引用类或名称空间成员或枚举器的名称.如果嵌套名称说明符中的:: scope resolution运算符前面没有decltype-specifier,则查找此前面的名称::仅考虑其专门化为类型的名称空间,类型和模板.如果找到的名称未指定名称空间或类,枚举或依赖类型,则程序格式错误.
14.2模板特化名称[temp.names]
对于要由模板参数显式限定的模板名称,必须知道名称才能引用模板.
名称查找(3.4)后发现名称是模板名称或者operator-function-id或literal-operator-id引用一组重载函数,如果后面跟着
<<函数模板,则其中任何成员都是函数模板a ,始终作为template-argument-list的分隔符,而不是less-than运算符. …
在以下代码中,f(int)选择重载而不是f(unsigned).用clang 3.0和gcc 4.8测试.
enum E
{
};
E f(int);
int f(unsigned);
E e = f(E(0));
Run Code Online (Sandbox Code Playgroud)
我对该标准的阅读使我认为enum -> int并且enum -> unsigned是相同的标准转换序列,它们都只包含整数转换.
[conv.integral]枚举类型的右值可以转换为整数类型的右值.
根据[over.best.ics],仅包含积分转换的标准转换序列的等级是"转换".
[over.ics.rank]两个相同形式的隐式转换序列是无法区分的转换序列,除非以下规则之一适用:[...]
在比较两个标准转换序列时,所提到的规则似乎都不适用.
我错过了什么?
C++ 11标准的N3337草案声明 [namespace.udecl]
using声明在声明区域中引入了一个名称,其中出现using声明.
每个using声明都是声明和成员声明,因此可以在类定义中使用.
在用作成员声明的using声明中,嵌套名称说明符应命名要定义的类的基类.
这通常用于在派生类的基类public中创建受保护的typedef,如下例所示,它在最新版本的Clang中成功编译:
struct A
{
protected:
typedef int Type;
};
struct B : A
{
using A::Type;
};
B::Type x;
Run Code Online (Sandbox Code Playgroud)
using-declaration可以引用模板类.这编译:
struct A
{
protected:
template<typename T>
struct Type
{
};
};
struct B : A
{
using A::Type;
};
B::Type<int> x;
Run Code Online (Sandbox Code Playgroud)
也可以引用依赖基类中的模板.以下编译成功(使用typedef注释.)
template<typename T>
struct A
{
protected:
template<typename U>
struct Type
{
};
};
template<typename T>
struct B : A<T>
{
using /* typename */ A<T>::Type; // A<T> is dependent, typename required?
// …Run Code Online (Sandbox Code Playgroud) c++ templates dependent-name using-declaration language-lawyer
gcc 5.0和clang 3.6都需要typename以下示例中的关键字:
template<typename T>
struct B
{
typedef int Type;
};
template<int n>
struct A
{
typedef typename B<decltype(throw (int*)n)>::Type Throw;
typedef typename B<decltype(delete (int*)n)>::Type Delete;
};
Run Code Online (Sandbox Code Playgroud)
This is covered by the following wording in the C++11 standard:
[except]/2
A throw-expression is of type void.
[expr.delete]/1
The operand shall have a pointer to object type, or a class type having a single non-explicit conversion function to a pointer to object type. The result has type void.
So …
在下面的示例中,A有一个成员typedef Instantiate导致实例化B<A>.
template<typename T>
struct B
{
typedef typename T::Before Before; // ok
typedef typename T::After After; // error: no type named 'After' in 'A<int>'
};
template<typename T>
struct A
{
typedef int Before;
typedef typename B<A>::After Instantiate;
typedef int After;
};
template struct A<int>; // instantiate A<int>
Run Code Online (Sandbox Code Playgroud)
我尝试过的所有编译器都报告说,虽然A::Before可见但A::After并非如此.这种行为是否符合标准?如果是这样,标准在哪里指定A在实例化期间哪些名称应该可见B<A>?
如果依赖名称"在模板实例化时查找",那么在由模板参数限定的名称场景中,这意味着什么T::After?
编辑:请注意,当A不是模板时,会发生相同的行为:
template<typename T>
struct B
{
typedef typename T::Before Before; // ok
typedef typename T::After …Run Code Online (Sandbox Code Playgroud) 在以下示例中,应调用哪个转换函数?为什么要选择那个呢?
struct A
{
operator int();
operator int*();
};
A x;
int i = x + 1;
Run Code Online (Sandbox Code Playgroud)
编译器选择operator int()..但为什么?
以下是来自C++ 03的一些相关引用:
来自[expr.add]
另外,两个操作数都应具有算术或枚举类型,或者一个操作数应是指向完全定义的对象类型的指针,另一个操作数应具有整数或枚举类型.
来自[转]
具有给定类型的表达式将在多个上下文中隐式转换为其他类型:
- 用作运算符的操作数时.运营商对其操作数的要求决定了目的地类型
c++ overloading type-conversion language-lawyer implicit-conversion
当前的C++编译器(最新的gcc,clang)需要typename以下示例中的关键字:
template<class T>
struct A
{
};
template<class T>
void f(T)
{
struct C
{
};
typedef typename A<C>::Type Type; // typename required
}
Run Code Online (Sandbox Code Playgroud)
如果typename省略,gcc(4.9,5.0)报告错误:
need 'typename' before 'A<f(T)::C>::Type' because 'A<f(T)::C>' is a dependent scope
根据我对C++ 11标准的阅读,这个例子是完善的.
这种行为似乎包含在以下措辞中:
[temp.dep.type]/8
如果是,则类型依赖于
模板参数,
一个未知专业的成员,
嵌套类或枚举,它是当前实例化的成员,
一个cv限定类型,其中cv-unqualified类型依赖于,
由任何依赖类型构成的复合类型,
从任何依赖类型构造的数组类型,或者其大小由与值相关的常量表达式指定,
一个simple-template-id,其中模板名称是模板参数或任何模板参数是依赖类型或依赖于类型或依赖于值的表达式,或者
由decltype(表达式)表示,其中表达式是类型依赖的.
但是,根据[class.local],类C是本地类而不是嵌套类.如果是这样,为什么应该A<C>被视为依赖?
编辑
对于奖励积分,如果通过添加成员枚举来修改示例C,如下所示:
template<typename T>
struct A
{
typedef T Type;
};
template<class T>
void f(T)
{
struct …Run Code Online (Sandbox Code Playgroud) c++ ×13
language-lawyer ×12
templates ×10
overloading ×3
compiler-bug ×2
c++11 ×1
local-class ×1
standards ×1
stl ×1
typename ×1