views:

1658

answers:

3

Here is some code outlining a problem I've been wrestling with. The final problem (as far as g++ is concerned at the moment) is that: "error: 'Foo-T' was not declared in this scope" when performing the Bar::Bar(...) constructor routine. Otherwise, the problem I'm attempting to learn my way through is one of setting base-class member types based on arguments passed to a derived-class constructor using templates. If there were a way to set the base-class member type (T Foo-T) simply by passing arguments to the derived-class constructor, I would prefer that. As of now I can't see a way past using both the template argument and a matching derived-class constructor argument to accomplish this task. Can you spot anything in the following code that I can be doing better to achieve the same goals? I'm rather new to generic-coding and templates.

#include <iostream>
typedef int a_arg_t;
typedef double b_arg_t;
typedef std::string foo_arg_t;

class TypeA {
public:
    TypeA ();
    TypeA (a_arg_t a) {
        /* Do sosmething with the parameter passed in */
    }

};

class TypeB {
public:
    TypeB ();
    TypeB (b_arg_t b) {
        /* typeB's constructor - do something here */
    }

};

// The base-class with a member-type to be determined by the template argument
template <class T>
class Foo {

public:
    Foo (const foo_arg_t foo_arg) : _foo_arg(foo_arg)       // initialize something here
    {
        /* do something for foo */
    }
    T Foo_T;        // either a TypeA or a TypeB - TBD
    foo_arg_t _foo_arg;
};

// the derived class that should set the basse-member type (T Foo_T)
template <class T>
class Bar : public Foo<T> {
public:
    Bar (const foo_arg_t bar_arg, const a_arg_t a_arg)
    : Foo<T>(bar_arg)   // base-class initializer
    {
        // the initialization of Foo_T has to be done outside the initializer list because it's not in scsope until here
        Foo_T = TypeA(a_arg);   // if an a_arg_t is passed in, then we set the Foo_T to TypeA, etc.
    }

    Bar (const foo_arg_t bar_arg, const b_arg_t b_arg)
    : Foo<T>(bar_arg)
    {
        Foo_T = TypeB(b_arg);
    }

};

int main () {

    b_arg_t b_arg;
    a_arg_t a_arg;
    foo_arg_t bar_arg;

    Bar<TypeA> a (bar_arg, a_arg);  // try creating the derived class using TypeA
    Bar<TypeB> b (bar_arg, b_arg); // and another type for show

return 0;
}
+2  A: 

Sorry to be unhelpful, but I also do not see a way around this without doing exactly what you stated:

As of now I can't see a way past using both the template argument and a matching derived-class constructor argument to accomplish this task.

You will likely have to specialize a bit:

template<>
Bar<TypeA>::Bar (const foo_arg_t bar_arg, const a_arg_t a_arg)
: Foo<TypeA>(bar_arg)   // base-class initializer
{
    // the initialization of Foo_T has to be done outside the initializer list because it's not in scsope until here
    Foo_T = TypeA(a_arg);   // if an a_arg_t is passed in, then we set the Foo_T to TypeA, etc.
}

template< class T>
Bar<T>::Bar (const foo_arg_t bar_arg, const a_arg_t a_arg)
: Foo<T>(bar_arg)   // base-class initializer
{
    // Throw exception?
}

template<>
Bar<TypeB>::Bar (const foo_arg_t bar_arg, const b_arg_t b_arg)
: Foo<TypeB>(bar_arg)
{
    Foo_T = TypeB(b_arg);
}

template< class T >
Bar<T>::Bar (const foo_arg_t bar_arg, const b_arg_t b_arg)
: Foo<T>(bar_arg)
{
    // Throw exception ?
}

Unfortunately, I do not have access to a compiler at the moment to check this code so be wary.


In answer to your question/comment. I got the following to compile:

#include <iostream>
typedef int a_arg_t;
typedef double b_arg_t;
typedef std::string foo_arg_t;

class TypeA {
public:
  TypeA () {}
  TypeA (a_arg_t a) {}
};

class TypeB {
public:
  TypeB () {}
  TypeB (b_arg_t b) {}
};

template <class T>
class Foo {
public:
  Foo (const foo_arg_t foo_arg) : _foo_arg(foo_arg) {}
  T Foo_T;        // either a TypeA or a TypeB - TBD
  foo_arg_t _foo_arg;
};

// the derived class that should set the basse-member type (T Foo_T)
template <class T>
class Bar : public Foo<T> {
public:
  Bar (const foo_arg_t bar_arg, const a_arg_t a_arg)
  : Foo<T>(bar_arg)   // base-class initializer
  {
    Foo<T>::Foo_T = TypeA(a_arg);
  }

  Bar (const foo_arg_t bar_arg, const b_arg_t b_arg)
  : Foo<T>(bar_arg)
  {
    Foo<T>::Foo_T = TypeB(b_arg);
  }
};

int main () {
  b_arg_t b_arg;
  a_arg_t a_arg;
  foo_arg_t bar_arg;

  Bar<TypeA> a (bar_arg, a_arg);  // try creating the derived class using TypeA
  Bar<TypeB> b (bar_arg, b_arg); // and another type for show

  return 0;
}
Jesse
Any idea about the compiler error stating that Foo_T is out of scope in the Bar::Bar(...) constructor? Doesn't the constructor of a derived class have access to the public or protected members of the base class? I realize that the base-class data members cannot be accessed in the initializer list, but I was always under the impression that the derived-classs constructor had access...
Shamster
Edited my answer to get your code to compile. To be honest...I am not sure why I needed explicitly say Foo<T>::Foo_T.
Jesse
Had some copy and paste errors. Try the code now.
Jesse
A: 

When I first looked at your code, I was absolutely certain that you would have to resolve the problem with partial specializing. Indeed, that may still be the case, however, I have reduced the amount of code necessary to replicate your error, and have observed that the error only occurs when compiling with gcc (I don't know which compiler version my university is running though), and when compiling with Visual Studio 2003 - everythying is happy.

The following replicates the error code, but a minor, seemingly innocent change will suprisingly eliminate it:

template <typename T>
class ClassA
{
public:
    ClassA () {}
    T vA;
};


template<typename T>
class ClassB : public ClassA<T>
{
public:
    ClassB ()
    {
     vA = 6;
    }
};

int main ()
{
    ClassB<int> cb;
}

Now, if you remove the template declaration from ClassB, and have it directly inherit from ClassA:

class ClassB : public ClassA<int>
{
public:
    ClassB ()
    {
     vA = 6;
    }
};

and then change the declaration of cb to match

    ClassB cb;

Then the error vanishes, even though there is clearly nothing different regarding the scope of vA (or in your case, Foo_T)

I'm speculating that this is a compiler bug, and wondering if perhaps a completely up to date gcc compiler would still experience the issue.

csj
I just read the update to another reply, and tried the scope resolution as directed. Lo and behold, it compiled.ClassA<T>::vA = 6;I'm a bit surprised that gcc demands this resolution when it appears to me that even with the templated inheritence the scope should be clear. Perhaps there's a nitpicky detail in the standard that would clear this up.
csj
+2  A: 

The Foo_T type will not be looked up in the base class when used in the derived (Bar) constructor.

Bar (const foo_arg_t bar_arg, const a_arg_t a_arg)
: Foo<T>(bar_arg)   // base-class initializer
{
    Foo_T = TypeA(a_arg);   TypeA, etc. // Won't compile, per the standard
}

This is per the C++ standard, which says unqualified names are generally non-dependent, and should be looked up when the template is fully defined.

Since a template base class definition is not known at that time (there could be fully specialised instances of the template being pulled in later in the compilation unit), unqualified names are never resolved to names in dependent base classes.

If you need a name from a base class when templates are involved, you have to either fully qualify them, or make them implicitly dependent in your derived class.

 Foo< T >::Foo_T = TypeA(a_arg);   // fully qualified will compile

or, make it dependent

 this->Foo_T = TypeA(a_arg);

Since the this makes it template dependent, resolving the type is postponed till "phase 2" of template instantiation (and then, the base class is also fully known)

Note that if you wanted to use a function from the base class, you could have also added a using declaration..

(inside Bar())

  some_foo_func(); // wouldn't work either

  using Foo<T>::some_foo_func;
  some_foo_func(); // would work however
Pieter