Why Do I Have to Access Template Base Class Members Through the This Pointer

Why do I have to access template base class members through the this pointer?

Short answer: in order to make x a dependent name, so that lookup is deferred until the template parameter is known.

Long answer: when a compiler sees a template, it is supposed to perform certain checks immediately, without seeing the template parameter. Others are deferred until the parameter is known. It's called two-phase compilation, and MSVC doesn't do it but it's required by the standard and implemented by the other major compilers. If you like, the compiler must compile the template as soon as it sees it (to some kind of internal parse tree representation), and defer compiling the instantiation until later.

The checks that are performed on the template itself, rather than on particular instantiations of it, require that the compiler be able to resolve the grammar of the code in the template.

In C++ (and C), in order to resolve the grammar of code, you sometimes need to know whether something is a type or not. For example:

#if WANT_POINTER
typedef int A;
#else
int A;
#endif
static const int x = 2;
template <typename T> void foo() { A *x = 0; }

if A is a type, that declares a pointer (with no effect other than to shadow the global x). If A is an object, that's multiplication (and barring some operator overloading it's illegal, assigning to an rvalue). If it is wrong, this error must be diagnosed in phase 1, it's defined by the standard to be an error in the template, not in some particular instantiation of it. Even if the template is never instantiated, if A is an int then the above code is ill-formed and must be diagnosed, just as it would be if foo wasn't a template at all, but a plain function.

Now, the standard says that names which aren't dependent on template parameters must be resolvable in phase 1. A here is not a dependent name, it refers to the same thing regardless of type T. So it needs to be defined before the template is defined in order to be found and checked in phase 1.

T::A would be a name that depends on T. We can't possibly know in phase 1 whether that's a type or not. The type which will eventually be used as T in an instantiation quite likely isn't even defined yet, and even if it was we don't know which type(s) will be used as our template parameter. But we have to resolve the grammar in order to do our precious phase 1 checks for ill-formed templates. So the standard has a rule for dependent names - the compiler must assume that they're non-types, unless qualified with typename to specify that they are types, or used in certain unambiguous contexts. For example in template <typename T> struct Foo : T::A {};, T::A is used as a base class and hence is unambiguously a type. If Foo is instantiated with some type that has a data member A instead of a nested type A, that's an error in the code doing the instantiation (phase 2), not an error in the template (phase 1).

But what about a class template with a dependent base class?

template <typename T>
struct Foo : Bar<T> {
Foo() { A *x = 0; }
};

Is A a dependent name or not? With base classes, any name could appear in the base class. So we could say that A is a dependent name, and treat it as a non-type. This would have the undesirable effect that every name in Foo is dependent, and hence every type used in Foo (except built-in types) has to be qualified. Inside of Foo, you'd have to write:

typename std::string s = "hello, world";

because std::string would be a dependent name, and hence assumed to be a non-type unless specified otherwise. Ouch!

A second problem with allowing your preferred code (return x;) is that even if Bar is defined before Foo, and x isn't a member in that definition, someone could later define a specialization of Bar for some type Baz, such that Bar<Baz> does have a data member x, and then instantiate Foo<Baz>. So in that instantiation, your template would return the data member instead of returning the global x. Or conversely if the base template definition of Bar had x, they could define a specialization without it, and your template would look for a global x to return in Foo<Baz>. I think this was judged to be just as surprising and distressing as the problem you have, but it's silently surprising, as opposed to throwing a surprising error.

To avoid these problems, the standard in effect says that dependent base classes of class templates just aren't considered for search unless explicitly requested. This stops everything from being dependent just because it could be found in a dependent base. It also has the undesirable effect that you're seeing - you have to qualify stuff from the base class or it's not found. There are three common ways to make A dependent:

  • using Bar<T>::A; in the class - A now refers to something in Bar<T>, hence dependent.
  • Bar<T>::A *x = 0; at point of use - Again, A is definitely in Bar<T>. This is multiplication since typename wasn't used, so possibly a bad example, but we'll have to wait until instantiation to find out whether operator*(Bar<T>::A, x) returns an rvalue. Who knows, maybe it does...
  • this->A; at point of use - A is a member, so if it's not in Foo, it must be in the base class, again the standard says this makes it dependent.

Two-phase compilation is fiddly and difficult, and introduces some surprising requirements for extra verbiage in your code. But rather like democracy it's probably the worst possible way of doing things, apart from all the others.

You could reasonably argue that in your example, return x; doesn't make sense if x is a nested type in the base class, so the language should (a) say that it's a dependent name and (2) treat it as a non-type, and your code would work without this->. To an extent you're the victim of collateral damage from the solution to a problem that doesn't apply in your case, but there's still the issue of your base class potentially introducing names under you that shadow globals, or not having names you thought they had, and a global being found instead.

You could also possibly argue that the default should be the opposite for dependent names (assume type unless somehow specified to be an object), or that the default should be more context sensitive (in std::string s = "";, std::string could be read as a type since nothing else makes grammatical sense, even though std::string *s = 0; is ambiguous). Again, I don't know quite how the rules were agreed. My guess is that the number of pages of text that would be required, mitigated against creating a lot of specific rules for which contexts take a type and which a non-type.

Access to derived class members through a base class reference

You cannot rebind references like you do. rBase already has a value and cannot be assigned to again. why doesn't C++ allow rebinding a reference?

So just make a new reference:

int main(int, char* [])
{
int iErr = 0;
stBase aBase(0);
stDeriv aDeriv(1);
stDeriv& rDeriv = aDeriv;
stBase& rBase = aBase;

rBase.Hello(); // OK result : "Hello from stBase"
rDeriv.Hello(); // OK result : "Hello from stDeriv"

// Make a new reference and all is fine
stBase& rBase2 = rDeriv;
rBase2.Hello(); // OK result : "Hello from stDeriv"

return iErr;
}

Double derived class produces error unless call to Base [duplicate]

In the class Derived the name print_pi is a dependent name.

You can use for example

template <typename T3>
class DoubleDerived : public Derived<T3> {
public:
void test() {
Derived<T3>::template print_pi<int>();
}
};

or

template <typename T3>
class DoubleDerived : public Derived<T3> {
public:
void test() {
DoubleDerived::template print_pi<int>();
}
};

Issue when calling template function that returns a pointer to base class

Your map expects pointers to free-standing functions, but your createT() is a non-static class method, so it is not compatible (also, your createT() is not actually return'ing anything).

When insert()'ing into your map, you are calling createT() and then trying to insert its returned object pointer, when you should instead be inserting the address of createT() itself, eg:

Model.h

#include <Base.h>
#include <memory>

class myClass {

template<typename T>
static std::unique_ptr<Base> createT() { return std::make_unique<T>(); }

typedef std::map<std::string, std::unique_ptr<Base>(*)()> map_type;
static map_type& getMap() {
static map_type instance;
return instance;
}

template<typename T>
static void registerT(const std::string& s) {
getMap().emplace(s, &myClass::createT<T>);
}

static auto getValue(const std::string& s) {
return getMap()[s];
}
};

Model.cc

#include <Model.h>
#include <DerivedA.h>

myClass::registerT<DerivedA>("DerivedA");
myClass::registerT<DerivedB>("DerivedB");

auto createA = myClass::getValue("DerivedA");
auto objA = createA();
objA->init(); // virtual
objA->run(); // virtual

However, you said in your description that you want to store object pointers in the map, not function pointers. Your code does not match your description. In which case, you would need something more like this instead:

Model.h

#include <Base.h>
#include <memory>

class myClass {

template<typename T>
static std::unique_ptr<Base> createT() { return std::make_unique<T>(); }

typedef std::map<std::string, std::unique_ptr<Base>> map_type;
static map_type& getMap() {
static map_type instance;
return instance;
}

template<typename T>
static void registerT(const std::string& s) {
getMap().emplace(s, createT<T>());
}

static auto& getValue(const std::string& s) {
return getMap()[s];
}
};

Model.cc

#include <Model.h>
#include <DerivedA.h>

myClass::registerT<DerivedA>("DerivedA");
myClass::registerT<DerivedB>("DerivedB");

auto &objA = myClass::getValue("DerivedA");
objA->init(); // virtual
objA->run(); // virtual

Can not access member type from base-class when compiled using c++17 [duplicate]

When the base class B class depends upon the template parameters, even though derived class D here type alias AbcData inherited from the B, using simply AbcData in D class, is not enough.

You need to be explicit, from where you have it

template<typename ...Args>
class D : public B<float, Args...>
{
public:
typename B<float, Args...>::AbcData m_abc; // --> like this
};

Strange template class conversion through the base class pointer

See static_cast from cppreference:


  1. If new_type is a reference or pointer to some class D and expression is lvalue of its non-virtual base B or prvalue pointer to it, static_cast performs a downcast. (This downcast is ill-formed if B is ambiguous, inaccessible, or virtual base (or a base of a virtual base) of D.) Such a downcast makes no runtime checks to ensure that the object's runtime type is actually D, and may only be used safely if this precondition is guaranteed by other means, such as when implementing static polymorphism. Safe downcast may be done with dynamic_cast.

Your code invokes undefined behavior, because the cast is ill-formed. You should use dynamic_cast for up/downcasts. Provide a virtual destructor in Base then this code:

#include <iostream>

class Base
{
public:
Base() = default;
virtual ~Base() = default;
};

template<typename NumericType>
class Numeric : public Base
{
public:
Numeric() : m_value() {}

void setValue(NumericType value) { m_value = value; }
NumericType value() const { return m_value; }

private:
NumericType m_value;
};

int main()
{
auto integerPtr = new Numeric<int>();
Base *basePtr = integerPtr;

if (auto doublePtr = dynamic_cast<Numeric<double>*>(basePtr)) {
doublePtr->setValue(6543423.634234);
std::cout << "Wow: " << doublePtr->value() << std::endl;
}

return 0;
}

Is correct and produces no output as expected.

Why this code is valid in C++

It is not valid code. Remember that incorrect code does not necessarily produce compiler errors. Anything can happen when your code has undefined behavior.

PS: I would have expected your code to blow up when optimizations are turned on. To my surprise, it still appears to work (https://godbolt.org/z/czfobbfqo). Thats the nasty side of undefined behavior. It may go unnoticed and will only blow up once you shipped the code and make a demo in front of your customers ;).



Related Topics



Leave a reply



Submit