4

how to define a template class inherit from template class ?

I want to wrap std::queue and std::priority_queue to a base class. In my case is LooperQueue. I use StdQueue in this way auto queue = new StdQueue<LooperMessage *>().

my class define compiler complain

error log:

  In file included from /Users/rqg/ASProjects/PboTest/muses/src/main/cpp/Painter.cpp:10:
  /Users/rqg/ASProjects/PboTest/muses/src/main/cpp/util/StdQueue.h:14:5: error: unknown type name 'size_type'; did you mean 'size_t'?
      size_type size() override;
      ^~~~~~~~~
      size_t
  /Users/rqg/Library/Android/sdk/ndk-bundle/toolchains/llvm/prebuilt/darwin-x86_64/lib64/clang/5.0.300080/include/stddef.h:62:23: note: 'size_t' declared here
  typedef __SIZE_TYPE__ size_t;
                        ^
  In file included from /Users/rqg/ASProjects/PboTest/muses/src/main/cpp/Painter.cpp:10:
  /Users/rqg/ASProjects/PboTest/muses/src/main/cpp/util/StdQueue.h:16:5: error: unknown type name 'reference'
      reference front() override;
      ^
  /Users/rqg/ASProjects/PboTest/muses/src/main/cpp/util/StdQueue.h:20:21: error: unknown type name 'value_type'; did you mean 'ARect::value_type'?
      void push(const value_type &x) override;
                      ^~~~~~~~~~
                      ARect::value_type
  /Users/rqg/Library/Android/sdk/ndk-bundle/sysroot/usr/include/android/rect.h:44:21: note: 'ARect::value_type' declared here
      typedef int32_t value_type;

code:

#ifndef PBOTEST_LOOPERQUEUE_H
#define PBOTEST_LOOPERQUEUE_H

#include <queue>
#include <cstdlib>

template<typename Tp, typename Sequence = std::deque<Tp> >
class LooperQueue {
  public:

    typedef typename Sequence::value_type                value_type;
    typedef typename Sequence::reference                 reference;
    typedef typename Sequence::const_reference           const_reference;
    typedef typename Sequence::size_type                 size_type;
    typedef          Sequence                            container_type;


    virtual size_type size()  = 0;

    virtual reference front() = 0;

    virtual void pop()= 0;

    virtual void push(const value_type &x) = 0;
};

#endif //PBOTEST_LOOPERQUEUE_H
#ifndef PBOTEST_STDQUEUE_H
#define PBOTEST_STDQUEUE_H

#include "LooperQueue.h"

template<typename Tp, typename Sequence = std::deque<Tp> >
class StdQueue : public LooperQueue<Tp, Sequence> {
  public:
    size_type size() override;

    reference front() override;

    void pop() override;

    void push(const value_type &x) override;
    
  private:
    std::queue<Tp, Sequence> mQueue;
};

#endif //PBOTEST_STDQUEUE_H
ilya1725
  • 4,496
  • 7
  • 43
  • 68
Fantasy_RQG
  • 143
  • 1
  • 13
  • 2
    posting compiler errors without filenames and line numbers stinks. Also, I recommend using 'using' instead of typedef. The syntax is more clear and I think it can give better error messages sometimes, too. – xaxxon Jul 14 '17 at 04:14
  • 1
    Also, you are not allowed to use names that start with an underscore followed by a capital letter, or any name starting with two underscores. _Sequence, for example, is not allowed. https://stackoverflow.com/a/228797/493106 – xaxxon Jul 14 '17 at 04:21
  • 2
    @xaxxon any identifier with two consecutive underscores *anywhere* in it is not allowed – Ryan Haining Jul 14 '17 at 05:30
  • @xaxxon sorry about my error log post . I removed underscores, but error remain same. I don't understand about "`using` instead of typedef". is there more detail – Fantasy_RQG Jul 14 '17 at 05:31

3 Answers3

8

I'm going to use a simpler example that gives you the same error, consider a base with just one alias defined and a subclass that tries to use it:

template <typename T>
class Base {
 public:
  using value_type = T;
};

template <typename T>
class Derived : public Base<T> {
  value_type func();  // error
};

Because of the crazy nature of templates, the compiler can't know what value_type is at this point. You have to tell it that it comes from the Base class either by qualifying it:

template <typename T>
class Derived : public Base<T> {
  typename Base<T>::value_type func();
};

or telling the compiler with a using declaration that you are intending to use a base class type alias

template <typename T>
class Derived : public Base<T> {
  using typename Base<T>::value_type;
  value_type func();
};

The compiler can't actually know that Base<T> contains a value_type until it knows what T is and instantiates the template. Why can't it just look at the Base template? -- such a thing would be theoretically possible, but it doesn't know what specializations will be available. If somewhere else you had

template<>
class Base<int> {};

Then Derived<int> would have to look for value_type elsewhere in its scope, and that's what it does in your original code. It tries to find a value_type and fails. This behavior can lead to some surprising results:

using value_type = char;
template <typename T>
class Derived : public Base<T> {
  value_type func(); // this is the global value_type = char, always
};

For a more ground-up explanation on related topics you can read my medium post

Ryan Haining
  • 35,360
  • 15
  • 114
  • 174
3

The issue here is that the base class LooperQueue is a dependent base class, which depends on the template parameter Tp and Sequence, then its complete type can't be determined without knowing the template arguments. Standard C++ says that nondependent names (like size_type, reference and value_type) won't be looked up in dependent base classes.

To correct the code, it suffices to make the names qualified by the base class name; then these names can be looked up only at the time of instantiation, and at that time the exact base specialization that must be explored will be known. e.g.

template<typename _Tp, typename _Sequence = std::deque<_Tp> >
class StdQueue : public LooperQueue<_Tp, _Sequence> {
public:
    typename LooperQueue<_Tp, _Sequence>::::size_type size() override;
    typename LooperQueue<_Tp, _Sequence>::reference front() override;
    void pop() override;
    void push(const typename LooperQueue<_Tp, _Sequence>::value_type &__x) override;
private:
    std::queue<_Tp, _Sequence> mQueue;
};
songyuanyao
  • 169,198
  • 16
  • 310
  • 405
1

Most, if not all, of your compilation errors are due to the types in your base class not being examined during name lookup of your derived class. The C++ standard says that you should fully qualify the type names (see this question). In other words, the types in your template base class are not visible from your derived class without their being fully qualified. Below is a simple example that compiles and runs with g++-6.3.0 -std=c++14:

#include <iostream>
#include <deque>

using namespace std;

template <typename T, typename S = deque<T> >
class Base
{
public:
    typedef typename S::size_type size_type;

    virtual size_type size() = 0;
};


template <typename T, typename S = deque<T> >
class MyClass : public Base<T, S>
{
public:
    // type name has to be fully qualified
    typedef typename Base<T,S>::size_type size_type;

    // you could use "typename Base<T,S>::size_type" here instead
    size_type size() override { return 0; }
};


int main()
{
    MyClass<int> c;
    cout << c.size() << endl;
}
Luis Guzman
  • 996
  • 5
  • 8
  • `typedef typename Base::size_type size_type;` can be accomplished with `using typename Base::size_type;` – Ryan Haining Jul 14 '17 at 05:45
  • Yes, all of the `typedef`s can be accomplished with `using` (alias declarations), and I prefer to use alias declarations instead of `typedef`. However, I decided to leave the `typedef`s to make the example closer to Fantasy_RQG's original code. – Luis Guzman Jul 14 '17 at 05:59
  • your code works great , but I can't split function define into declaration and definition. Is there any way to do this ? – Fantasy_RQG Jul 14 '17 at 06:25
  • 1
    To split the function declaration from the definition, first leave the definition inside the class. It will look like this: `size_type size() override;` Then, after the class or in the `.C` file: `template typename MyClass::size_type MyClass::size() { return 0; }` You can write the definition on different lines, of course. If you want me to edit the code in the answer to reflect this let me know. – Luis Guzman Jul 14 '17 at 12:34
  • 1
    BTW if you place the template definition in a `.C` or `.cpp` file, you'll need to explicitly instantiate the template class for particular types, which is restricting (and a different subject). I usually just place the definitions in `.h` files. – Luis Guzman Jul 14 '17 at 12:58