Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cannot define member of dependent typedef

Tags:

c++

I am writing custom lazy string class.

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);
}

Then i want to define these methods outside of class declaration.

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)
{
    ...
}

But i got compile error:

cannot define member of dependent typedef char_proxy

So i can't figure out what's problem here. Why compiler can't use shortcut char_proxy instead of lazy_basic_string::char_proxy ?

like image 851
eupp Avatar asked Mar 16 '15 11:03

eupp


2 Answers

This doesn't seem to be particularly well-specified by the Standard. The closest I can appear to get is [temp.class]:

3 - When a member function, a member class, a member enumeration, a static data member or a member template of a class template is defined outside of the class template definition, the member definition is defined as a template definition in which the template-parameters are those of the class template. The names of the template parameters used in the definition of the member may be different from the template parameter names used in the class template definition. The template argument list following the class template name in the member definition shall name the parameters in the same order as the one used in the template parameter list of the member. [...]

This implies, though does not exactly state, that an out-of-line class template member definition should refer to the class template by its name, and not via an alias template.

It should be reasonably easy to see why this is necessary; as an alias template can result in an arbitrarily complex computation, in order to match a use of a class template member against a potential definition the compiler would have to perform that computation on every possible combination of alias 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?

Interestingly, clang (3.7) allows the use of an alias template in a class template member definition, but only where it is a straight identity calculation:

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??
like image 195
ecatmur Avatar answered Nov 10 '22 21:11

ecatmur


What compiler are you using? With GCC 4.8, I've found no way to make it compile, but on Visual Studio 2015 Preview, if you slightly modify your code, it compiles successfully:

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

or, if you prefer:

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

As you will have noticed, I could use the alias only either as a return type or to access the operator name, but not both.

I think you've found some kind of dark area in the standard, because the following code has different behaviors on VS and GCC too. It compiles on VS 2015, but not in 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{};
}

In this case, on VS I was able to use the alias both to access the function name and to specify the return type.

like image 31
Paolo M Avatar answered Nov 10 '22 23:11

Paolo M