9

I've been confused recently by a few code examples -- sometimes it seems that inheriting typedefs exposed by a base class works, and sometimes it seems that it doesn't.

My questions are

  • Why doesn't it always work?
  • What are the situations that it will / won't work?
  • What are the good workarounds when it doesn't work?

Here's some specific code:

// First example: Inheriting `static const int ...`
// Basic TypeList object
template<typename... Ts>
struct TypeList {
    static const int size = sizeof...(Ts);
};

// Repeat metafunction
template<typename T>
struct repeat;

template<typename... Ts>
struct repeat<TypeList<Ts...>> : TypeList<Ts..., Ts...> {};

// Checks
typedef TypeList<int, float, char> MyList;

static_assert(MyList::size == 3, "D:");
static_assert(repeat<MyList>::size == 6, "D:");


// Second example: Inheriting typedefs
// Meta function to compute a bundle of types
template <typename T>
struct FuncPtrTypes {
    typedef int result_type;
    typedef T input_type;
    typedef result_type(*func_ptr_type)(input_type);
};


// template <typename T, typename FuncPtrTypes<T>::func_ptr_type me>
// struct FuncPtr : FuncPtrTypes<T> {
//     static result_type apply(input_type i) {
//         return me(i);
//     }
// };
//
// Doesn't compile (?): clang 3.6:
// main.cpp:34:9: error: unknown type name 'result_type'
//         static result_type apply(input_type i) {
//                ^
// main.cpp:34:27: error: unknown type name 'input_type'
//         static result_type apply(input_type i) {
//                                  ^
//
// g++ 4.8.4:
// main.cpp:34:9: error: ‘result_type’ does not name a type
//   static result_type apply(input_type i) {
//          ^
// main.cpp:34:9: note: (perhaps ‘typename FuncPtrTypes<T>::result_type’ was intended)


// This compiles but is clumsy:

template <typename T, typename FuncPtrTypes<T>::func_ptr_type me>
struct FuncPtr {
    typedef typename FuncPtrTypes<T>::input_type input_type;
    typedef typename FuncPtrTypes<T>::result_type result_type;

    static result_type apply(input_type i) {
        return me(i);
    }
};


// A non-template example:
struct foo {
    typedef int bar;
};

struct baz : foo {};

typedef baz::bar bazbar;
// ^ This compiles... huh??

int main() {}
Chris Beck
  • 15,614
  • 4
  • 51
  • 87
  • 2
    They don't "work" if the base class is dependent on a template-parameter of the derived class, because the base class scope isn't searched from the point of definition of the derived class. It can't be searched, because you could still specialize the base class template after defining the derived class template. – dyp Aug 28 '15 at 12:04
  • dyp: I thought that none of it was actually instantiated though until all of parameters are available? Humm so it has to resolve all these types that I use at the point of definition of the derived class? – Chris Beck Aug 28 '15 at 12:07
  • 1
    @ChrisBeck: There are 2 passes to check validity in template. A first one with non-dependent code, and a second one for dependent code. `result_type` as-is is a non-dependent code and an unknown type. – Jarod42 Aug 28 '15 at 12:19
  • 1
    A syntactically elegant solution is the equivalent of replacing member functions with free functions: `template using input_type_t = typename T::input_type;` Then you can write `using base = FuncPtrTypes; using input_type = input_type_t;` etc. – dyp Aug 28 '15 at 13:10

2 Answers2

18

We can simplify your failing example down to:

template <typename T>
struct Base { using type = T; };


template <typename T>
struct Derived : Base<T>
{
    type mem; // error: 'type' does not name a type
};

The issue is that type here is a dependent name. It depends on T. There is no guarantee that for a given T there isn't some specialization of Base<T> which does not name type. As such, base templates of class templates are not part of the standard name lookup, so you have to qualify it:

Base<T>::type mem;

Although now we run afoul of the rule which indicates that dependent names are not assumed to be types unless explicitly states as such, so you need:

typename Base<T>::type mem;

None of the other cases presented in the OP rely upon unqualified lookup of a dependent name.

To return to the specific problem, this doesn't compile:

static result_type apply(input_type i) {

because result_type and input_type are dependent types and so must be qualified and prefixed with typename:

static typename FuncPtrTypes<T>::result_type apply(typename FuncPtrTypes<T>::input_type i) {

Or, if you prefer, you can simply bring both names in with a using-declaration:

 using typename FuncPtrTypes<T>::input_type;
 using typename FuncPtrTypes<T>::result_type;

 static result_type apply(input_type i) {
Barry
  • 286,269
  • 29
  • 621
  • 977
  • "There is no guarantee that for a given T there isn't some specialization of Base which does not name type." Could you elaborate on this? What do you mean when you say `Base` "does not name" a type? Could you give a simple example? – Ben Nov 22 '21 at 05:03
  • 1
    @Ben `template <> struct Base { };` – Barry Nov 22 '21 at 14:47
  • So wouldn't `Base::type` just be `int` in that case? And wouldn't it still be true that `Derived::type` would be `T` in all cases? I guess what I'm wondering is why the compiler can't simply infer that `Derived::type` is the same as `Base::type` for all `T`. – Ben Nov 22 '21 at 18:08
  • @Ben No, there's no `Base::type` - there's nothing in between the braces there. – Barry Nov 22 '21 at 18:27
  • I see. Suppose there are very many types (and possibly values, methods, etc.) from the base class that I want to use in the sub-class. It can be tedious to qualify these names every or even to have so many using declarations (and more might need to be added if the base class were changed). Is there a simpler way to indicate that all names from the parent class should also be used in the sub-class? – Ben Nov 27 '21 at 17:29
3

The code below will compile:

template <typename T, typename FuncPtrTypes<T>::func_ptr_type me>
 struct FuncPtr : FuncPtrTypes<T> {
     static typename FuncPtrTypes<T>::result_type apply(typename FuncPtrTypes<T>::input_type i) {
         return me(i);
     }
 };

The problem is basically that c++ templates have a two phased lookup during compilation. The first phase is a syntactical lookup, while the second phase performs the compilation in terms of the actual type.

To expand further:

The problem is that result_type might be either a global symbol or inherited, and during the first phase of lookup it is assumed that it is global, as the type that is dependent on T is not parsed/evaluated at that stage/phase of compilation.

See: where typenames are required

Another possible solution to your problem, is to use traits (e.g):

template <class T>
struct FuncPtrTraits
{
  typedef int result_type;
  typedef T input_type;
  typedef result_type(*func_ptr_type)(input_type);
};

 template <typename T, typename TraitsT = FuncPtrTraits<T> >
 struct FuncPtr
     static typename TraitsT::result_type apply(typename TraitsT::input_type i) {
         return me(i);
     }
 };

When designed correctly traits can help one modify code from the outside, and also partially, when you inherit from the base trait type.

Community
  • 1
  • 1
Werner Erasmus
  • 3,988
  • 17
  • 31
  • this isn't really what I was looking for, for instance the above will still compile even if the inheritance is removed, so you aren't really using inheritance here. but it still might be the best answer – Chris Beck Aug 28 '15 at 12:12
  • I've made a mod. I'm using traits instead of inheritance. Traits at least impose a convention on type T. You'll notice I've removed the inheritance, albeit after the fact. – Werner Erasmus Aug 28 '15 at 12:41
  • This **does** work well with inheritance, and it solved my problem. Thanks. – Elliott Feb 01 '20 at 04:33