Search code examples
c++c++11templatescrtpdecltype

C++11: find a method in a class and Error: incomplete type used in nested name specifier


With CRTP idiom in C++11 I'm trying to check out if a derived class has a method named size or not.

#include <iostream>

template<typename Derive> struct IsContainer {
    template<typename Der> static constexpr bool has_size( decltype(&Der::size) ) { return true;  }
    template<typename Der> static constexpr bool has_size(...)                    { return false; } 
                           static constexpr bool value = has_size<Derive>(nullptr);
};

struct Container : IsContainer<Container> {
    const unsigned int size();
};

struct NonContainer : IsContainer<NonContainer> {};

int main() {
    Container obj;
    std::cout << obj.value <<std::endl;
}

I then tried to get rid of template <typename Der> as following:

#include <iostream>

template<typename Derive> struct IsContainer {
    static constexpr bool has_size( decltype(&Derive::size) ) { return true;  }
    static constexpr bool has_size(...)                       { return false; }
    static constexpr bool value = has_size(nullptr);
};

struct Container : IsContainer<Container> {
    const unsigned int size();
};

struct NonContainer : IsContainer<NonContainer> {};

int main() {
    Container obj;
    std::cout << obj.value <<std::endl;
}

Compilation error:

   In instantiation of ‘struct IsContainer<Container>’:
      error: incomplete type ‘Container’ used in nested name specifier
     |         static constexpr bool has_size( decltype(&Derive::size) ) { return true;  }
     |                                                  ^~~~~~~~

I encountered such a statement quoted from standard explaining why does compiler gives this error

3.3.2p6 Declaration point [basic.scope.pdecl]

Once a class member has been declared, its name can be searched for within the scope of the class

I still do not get that for which reason the first code snippet does work and the second not. I really would appreciate if someone explains it to me with detail and possibly give me a hint to get around the error of the second code snippet.


Solution

  • By removing the template-head template <typename Der>, there can be no SFINAE for those member and so we get a hard error. SFINAE happens when during substitution of the template parameter, and without a template-head there is no substitution and hence no SFINAE. Note that in the second snippet, Derive is a template parameter of the class template and not the member function itself.

    Container isn't complete until the closing brace. So decltype(&Derive::size) in the member function parameter will give a hard error without the template head as no sfinae can happen unlike the first case. See the contrived example given at the end of the answer.


    When you wrote IsContainer<Container> then Derive= Container and since Container isn't complete at this point we can't write decltype(&Derive::size) which basically is decltype(&Container::size) in the parameter declaration. Note also that declarations of member functions are instantiated with the class template. And since in the second snippet, you don't have a template-head i.e., Derive is a template parameter of the class not the member function, we don't get SFINAE and instead get a hard error.

    Contrived Example

    Below is a convtrived example to illustrate the same. In particular, for the following program we will get a hard error as no SFINAE happens for func.

    struct C;
    
    template<typename T> struct D
    {
        void func(decltype(sizeof(T::type))); //NO SFINAE here so we will get a hard error
        void func();
    };
    int main()
    { 
         D<C> d;  
    }
    

    The error you get for the above program says:

    <source>: In instantiation of 'struct D<C>':
    <source>:11:11:   required from here
       11 |      D<C> d;
          |           ^
    <source>:7:34: error: incomplete type 'C' used in nested name specifier
        7 |     void func(decltype(sizeof(T::type))); //NO SFINAE here so we will get a hard error
          |       
    

    Now, let's look at another example where SFINAE happens and we don't get a hard error:

    struct C;
    
    template<typename T> struct D
    {
           template<typename U> void bar(decltype(sizeof(U::type))); //sfinae happens here so no hard error here
           void bar();
    };
    int main()
    { 
         D<C> d;  
    }