【问题标题】:Template class with nested template class, g++ compiles fine, vs2013 wont compile带有嵌套模板类的模板类,c++编译正常,vs2013不会编译
【发布时间】:2014-12-21 19:23:57
【问题描述】:

我正在实现自己的地图,这是一段代码。困扰我的部分代码是这两个声明:

template<typename KEY, typename VAL>
Map<KEY,VAL>::MapPair<KEY,VAL> Map<KEY,VAL>::make_map_pair(KEY k, VAL v){
    return MapPair<KEY,VAL>(k,v);
 }

template<typename KEY, typename VAL>
template<typename K, typename V>
Map<KEY,VAL>::MapPair<K,V>& Map<KEY,VAL>::MapPair<K,V>::setKey(K keyp, V val){
    key = keyp;
    value = val;
 }

类def如下:

 template <typename KEY, typename VAL>
 class Map{
 private:
    template<typename K, typename V>
    class MapPair {
    public:
        K key;
        V value;
        MapPair(){};
        MapPair(K key, V value);
        MapPair<K,V>& setKey(K key, V val);
        V& getValue();
        K getKey();
        bool operator==(MapPair<K,V> item);
    };

    List<MapPair<KEY,VAL>> pair_list_;
    MapPair<KEY,VAL> make_empty_map_pair(KEY k);
    MapPair<KEY,VAL> make_map_pair(KEY k, VAL v);
 public:
    Map(){}
    bool exists(KEY key);
    VAL& operator[](KEY key);
    VAL pop_pair(KEY key);
 };

此代码无需从 g++ 窥视即可编译,但 Visual Studio 2013 会出现错误:

error C2059: syntax error : ')'
error C2059: syntax error : ')'

每个顶级函数定义都会产生一个错误。

来自 debain 7.5 下的 g++

g++ -Wall -c -std=c++11  vm.cpp
g++ vm.o -o vm

而且它运行良好,没有运行时问题,并且可以做它应该做的事情。

问题是为什么g++运行得这么好,而vs2013却编译不出来?我该怎么做才能使这段代码在 vs2013 上正确编译?我可以培养什么样的习惯来确保我编写的代码的可移植性?

【问题讨论】:

  • 函数的返回类型应该声明为typename Map&lt;KEY,VAL&gt;::template MapPair&lt;K,V&gt;
  • 使用什么确切版本的 g++ 来编译这段代码?
  • 这和stackoverflow.com/questions/18344580/… 是同一个问题.. 还没有最终答案

标签: c++ templates visual-studio-2013 g++ nested-class


【解决方案1】:

事实证明,VS2013 喜欢在嵌套类的返回类型之前插入typename。我知道我“可以”这样做,但直到这个例子我才知道为什么我会这样做。

template<typename KEY, typename VAL>
typename Map<KEY,VAL>::MapPair<KEY,VAL> Map<KEY,VAL>::make_map_pair(KEY k, VAL v){
/*^^^^^ right here before the nested class object that it will return.*/
    return MapPair<KEY,VAL>(k,v);
}

最好的办法是使用它,这样代码可以通过较少的更改进行移植,即使 g++ 不需要它。

【讨论】:

  • 您缺少template,应该是typename Map&lt;KEY,VAL&gt;::template MapPair&lt;KEY,VAL&gt;
【解决方案2】:

我对 C++11 标准的阅读表明 typename 是必需的。 VS2013 做对了。

如果没有 typename 关键字,则假定依赖名称不命名类型。

14.6 名称解析 [temp.res]

2) 在模板声明或定义中使用并且依赖于模板参数的名称是 假定不命名类型,除非适用的名称查找找到类型名称或名称是合格的 通过关键字类型名。

3) 当限定 ID 旨在引用不是当前实例化成员的类型时 并且它的nested-name-specifier 指的是一个依赖类型,它应该以关键字typename 为前缀

7) 在类模板的定义内或在类模板成员的定义内 declarator-id,在引用先前声明的名称时不需要关键字 typename 声明类型的类模板的成员。 [注意:可以使用非限定名称找到此类名称 查找,类成员查找到当前实例化,或类成员访问 对象表达式的类型为当前实例化时的表达式查找

14.6.2.1 依赖类型 [temp.dep.type]

一个名字指的是当前实例化如果它是

  • 在主类模板或主类模板成员的定义中, 类模板后跟主模板的模板参数列表(如下所述) 包含在
  • 中

当Map&lt;KEY, VAL&gt; 用于Map 的定义中时,它指的是当前实例化。在解析make_map_pair 的定义时,Map&lt;KEY, VAL&gt;:: 限定的类型名称通常可以通过在当前实例中查找类成员名称来找到。

但是,当 C++ 解析器在成员函数定义的返回类型中遇到 Map&lt;KEY, VAL&gt; 时 - 在声明符 ID 之前 - 它还没有遇到封闭类的名称。此时解析器无法确定Map 是否引用了封闭类。

出于这个原因 - 无论Map&lt;KEY, VAL&gt; 是否命名当前实例化 - 标准都不允许在声明符 id 之前的类模板成员的定义中省略 typename。

Vaughn Cato 的这个 example 证明了 Clang/GCC 的行为是不一致的,并且在类似的场景中需要 typename:

template <typename T>
struct A {
    typedef int X;
    X f();
};

template <typename T>
A<T>::X A<T>::f() // error: missing 'typename'
{
}

如果我们断定名称 Map 是依赖的,并且需要 typename,则还需要 template 关键字:

14.2 模板特化的名称 [temp.names]

当成员模板特化的名称出现在后缀表达式中的 . 或 -&gt; 之后或之后 限定 id 中的嵌套名称说明符,以及后缀表达式的对象或指针表达式或 限定 ID 中的嵌套名称说明符取决于模板参数 (14.6.2),但不引用 当前实例化(14.6.2.1)的成员,成员模板名称必须以关键字为前缀 模板。否则,该名称被假定为命名非模板。

更正后的例子是:

template<typename KEY, typename VAL>
typename Map<KEY,VAL>::template MapPair<KEY,VAL>
    Map<KEY,VAL>::make_map_pair(KEY k, VAL v) {
    return MapPair<KEY,VAL>(k,v);
}

这里曾讨论过同样的问题:Can typename be omitted in the type-specifier of an out of line member definition?

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-07-26
    • 1970-01-01
    • 2019-02-25
    • 1970-01-01
    • 2021-11-10
    • 1970-01-01
    • 2018-03-25
    相关资源
    最近更新 更多