我正在编写自定义的懒字符串类。
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)
{
...
}
但是我收到了编译错误:
无法定义依赖typedef char_proxy
的成员
所以我无法弄清楚这里有什么问题。 为什么编译器不能使用快捷方式char_proxy而不是lazy_basic_string :: char_proxy?
答案 0 :(得分:4)
标准似乎没有特别明确规定。我能得到的最接近的是 [temp.class] :
3 - 当成员函数,成员类,成员枚举,静态数据成员或成员时 类模板的模板是在类模板定义之外定义的,成员定义是 定义为模板定义,其中 template-parameters 是类模板的定义。该 成员定义中使用的模板参数的名称可能与模板不同 类模板定义中使用的参数名称。类后面的模板参数列表 成员定义中的模板名称应按照与中使用的顺序相同的顺序命名参数 成员的模板参数列表。 [...]
这意味着,虽然没有准确说明,但是一个外行类模板成员定义应该通过名称引用类模板,而不是通过别名模板引用。
应该很容易理解为什么这是必要的;作为别名模板可能导致任意复杂的计算,为了匹配类模板成员与潜在定义的使用,编译器必须对别名模板参数的每个可能组合执行该计算:
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??
答案 1 :(得分:1)
您使用的是什么编译器?使用GCC 4.8,我发现无法编译,但在Visual Studio 2015 Preview中,如果稍微修改代码,它会成功编译:
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上我可以使用别名来访问函数名称并指定返回类型。