我正在尝试使用 g++ 编译之前在 Visual C++ 2008 Express Edition 下开发的一些代码,看起来 g++ 不允许我对模板变量的方法返回的引用调用模板方法。我能够将问题缩小到以下代码:
class Inner
{
public:
template<typename T>
T get() const
{
return static_cast<T>(value_);
};
private:
int value_;
};
class Outer
{
public:
Inner const& get_inner() { return inner_; };
private:
Inner inner_;
};
template<typename T>
int do_outer(T& val)
{
return val.get_inner().get<int>();
}
int main()
{
Outer outer;
do_outer(outer);
return 0;
}
代码在微软的编译器下编译得很好,但 g++ 会抛出错误:
$ g++ -c main.cpp
main.cpp: In function ‘int do_outer(T&)’:
main.cpp:24: error: expected primary-expression before ‘int’
main.cpp:24: error: expected ‘;’ before ‘int’
main.cpp:24: error: expected unqualified-id before ‘>’ token
其中第 24 行指的是
return val.get_inner().get<int>();
。
如果我将
do_outer
设为接收 Outer
引用的普通方法,则代码将进行编译。将 Inner::get()
设为普通方法也可以。使 Inner::get()
返回 void 并接收模板参数也可以工作,因为下面的 int 说明符变得不必要,即:
class Inner
{
public:
template<typename T>
void get(T& val) const
{
val = static_cast<T>(value_);
};
private:
int value_;
};
...
template<typename T>
int do_outer(T& val)
{
int i;
val.get_inner().get(i);
return i;
}
...
(g++ 不会抱怨上面的代码。)
现在我没有主意了。有什么问题吗? gcc/g++有问题吗?我的代码是否存在合规性问题?
我使用的编译器是:
$ g++ --version
g++ (Ubuntu 4.3.3-5ubuntu4) 4.3.3
只是为了提供一些关于为什么需要
template
关键字的背景知识:
template<typename T>
int do_outer(T& val)
{
int i;
val.get_inner().get<int>(i);
return i;
}
当编译器看到这个函数时,它不知道
val
的类型是什么。 因此,它将行 val.get_inner().get(i)
解析如下:
1:
val .
编译器看到
.
,因此可以假设 'val' 具有类类型,并且下一个标识符是成员对象或函数的名称。
2。
val . get_inner (
get_inner
是成员的名称,然后编译器会看到(
。 唯一的可能是 get_inner
是一个函数名,所以这是一个函数调用。 然后它解析参数,直到找到结束)
。
3.
val . get_inner () .
对于第一步,它现在知道 get_inner 返回的值必须是类类型,因此它知道下一个标识符是成员对象或函数。
4。
val . get_inner () . get <
那么,
<
可能意味着什么? 当然,它是模板参数的开始......或者也许它是小于运算符?
我们知道
get
只能是一个对象或一个函数。 如果它是一个对象,那么 <
作为小于运算符就非常有意义。 此外,标准或多或少规定,只有在 <
之前的名称是 template-name
时,才会将 <
视为模板参数 (14.2/3):
名称查找(3.4)发现名称是模板名称后,如果该名称后跟
,则<
始终被视为模板参数列表的开头,而不是名称后跟小于运算符。<
在这种情况下,编译器不知道表达式
val.get_inner()
的类型是什么,因此无法查找 get
。 它或多或少地假设它是一个成员对象而不是模板名称。 '<' is treated as the less than operator and the compiler ends up checking if get
小于 int
- 因此出现错误。
那么,为什么这些修复有效?
添加
template
关键字
从字面上看,我们告诉编译器
get
是模板名称,因此 <
运算符被视为模板参数列表的开头。
删除模板参数
当 do_outer 没有模板参数时,即:
val . get_inner () . get (
,编译器期望成员 get
是一个对象或函数。 (
消除了两者之间的歧义,并且名称被视为函数。 稍后模板参数推导即可计算出模板参数的类型。
你可以尝试一下吗?
template<typename T>
int do_outer(T& val)
{
return val.get_inner().template get<int>();
}
我无法访问 gcc atm,但我遇到过类似的问题,添加 template 关键字总是可以解决这些问题。而且它在 VS 中也有效。
我不能声称自己是地球上完全理解 C++ 模板的 10 个人之一,但你在这里所做的对我来说看起来很好。 (它在 GCC 4.4.1 上失败,并出现相同的错误,顺便说一句)。
将
do_outer
更改为
const Inner& inner = val.get_inner();
return inner.get<int>();
适用于 GCC,并且可能也适用于 Visual C++。
您可以考虑向 GCC 提交错误;要么他们会修复它,要么它将被关闭为无效,在此过程中有人希望解释为什么你正在做的不是有效的代码。
进一步更新和 AHA:事实证明它实际上不是有效的代码,GCC 只是给出了一条可怕的错误消息。英特尔 C++ 输出(实际上很有帮助!)错误消息:
template.cpp(24): error: type name is not allowed
return val.get_inner().get<int>();
这让我意识到了问题所在。将 do_inner 更改为
return val.get_inner().template get<int>();
该代码被 ICC 和 GCC 接受。