无法定义依赖类型定义的成员

Cannot define member of dependent typedef

本文关键字:定义 成员 类型 依赖      更新时间:2023-10-16

我正在编写自定义懒惰字符串类。

template <typename charT, typename traits = std::char_traits<charT>>
class lazy_basic_string
{
    class char_proxy
    {
        char_proxy& operator=(charT ch);
    };
    char_proxy operator[](size_type i);
}

然后我想在类声明之外定义这些方法。

template <typename charT, typename traits>
using char_proxy = typename lazy_basic_string<charT, traits>::char_proxy;
template <typename charT, typename traits>
char_proxy<charT, traits>& char_proxy<charT, traits>::operator=(charT ch)
{
    ...
}

但是我得到了编译错误:

无法定义依赖类型定义的成员char_proxy

所以我无法弄清楚这里有什么问题。为什么编译器不能使用快捷方式char_proxy而不是lazy_basic_string::char_proxy?

标准似乎并没有特别明确规定这一点。我能得到的最接近的是[temp.class]

3 - 当成员函数、成员类、成员枚举、静态数据成员或成员时 类模板的模板是在类模板定义之外定义的,成员定义是 定义为模板定义,其中模板参数是类模板的参数。这 成员定义中使用的模板参数的名称可能与模板不同 类模板定义中使用的参数名称。类后面的模板参数列表 成员定义中的模板名称应按照与 成员的模板参数列表。[...]

这意味着(尽管没有完全说明)外类模板成员定义应按其名称引用类模板,而不是通过别名模板引用。

应该很容易理解为什么这是必要的;由于别名模板可能导致任意复杂的计算,为了将类模板成员的使用与潜在定义相匹配,编译器必须对别名模板参数的每个可能组合执行该计算:

template<class T> struct S { void f(); };
template<class T> using s_t = std::conditional_t<sizeof(T) % 8 == 0,
    S<T>, S<T*>>;
template<class T> void s_t<T>::f() {}
int main() { S<int> s; s.f(); }    // defined?

有趣的是,clang (3.7) 允许在类模板成员定义中使用别名模板,但仅限于直接标识计算的情况:

template<class> struct T { void f(); };
template<class C> using id_t = C;
template<class C> using t_t = T<id_t<C>>;
template<class C> void t_t<C>::f() {}    // OK??

你用的是什么编译器?在GCC 4.8中,我发现没有办法让它编译,但是在Visual Studio 2015预览版上,如果你稍微修改你的代码,它就会成功编译:

template <typename charT, typename traits>
char_proxy<charT, traits>& lazy_basic_string<charT, traits>::char_proxy::operator=(charT ch)
{
    return {};
}

或者,如果您愿意:

template <typename charT, typename traits>
typename lazy_basic_string<charT, traits>::char_proxy& char_proxy<charT, traits>::operator=(charT ch)
{
    return{};
}

正如您可能已经注意到的,我只能将别名用作返回类型或访问运算符名称,但不能同时使用两者。

我想你已经在标准中找到了某种暗区,因为下面的代码在 VS 和 GCC 上也有不同的行为。它在VS 2015上编译,但不在GCC中编译:

template<typename T>
class A {
    class B {
        B& test();
    };
};
template<typename T>
using B_alias = typename A<T>::B;
template<typename T>
B_alias<T>& B_alias<T>::test()
{
    return{};
}

在这种情况下,在 VS 上,我能够使用别名来访问函数名称和指定返回类型。