Why Do I Need to Repeat Template Arguments of My Base Class in Member Initalizer List

Why do I need to repeat template arguments of my base class in member initalizer list?

You only need to do that when Derived is a template and the type of the base depends on its template parameters.

This compiles, for example:

template <typename T>
class Derived : public Base<int, false>
{
public:
Derived(const T& t) : Base(t) {}
};

As far as I know, here (in member initializer list) Base is actually the injected-class-name of Base<...>, inherited from it like everything else.

And if the type of the base does depend on the template parameters, its inherited injected-class-name becomes inaccessible (at least directly), just like any other member inherited from it.

For a member variable/function, you'd add this-> to access it, but for a type member you need Derived:::

template <typename T>
class Derived : public Base<T, false>
{
public:
Derived(const T& t) : Derived::Base(t) {}
};

Template base constructor call in member initialization list error

First:

[C++11: 12.6.2/3]: A mem-initializer-list can initialize a base class using any class-or-decltype that denotes that base class type.

[ Example:

struct A { A(); };
typedef A global_A;
struct B { };
struct C: public A, public B { C(); };
C::C(): global_A() { } // mem-initializer for base A

—end example ]

And Base should be a valid injected-class-name for the base here (that is, you can use it in place of Base<T>):

[C++11: 14.6.1/1]: Like normal (non-template) classes, class templates have an injected-class-name (Clause 9). The injected-class-name can be used as a template-name or a type-name. When it is used with a template-argument-list, as a template-argument for a template template-parameter, or as the final identifier in the elaborated-type-specifier
of a friend class template declaration, it refers to the class template itself. Otherwise, it is equivalent to the template-name followed by the template-parameters of the class template enclosed in <>.

[C++11: 14.6.1/3]: The injected-class-name of a class template or class template specialization can be used either as a template-name or a type-name wherever it is in scope. [ Example:

template <class T> struct Base {
Base* p;
};

template <class T> struct Derived: public Base<T> {
typename Derived::Base* p; // meaning Derived::Base<T>
};

template<class T, template<class> class U = T::template Base> struct Third { };
Third<Base<int> > t; // OK: default argument uses injected-class-name as a template

—end example ]

I haven't found anything to indicate that this doesn't apply in the ctor-initializer, so I'd say that this is a compiler bug.

My stripped-down testcase fails in GCC 4.1.2 and GCC 4.3.4 but succeeds in GCC 4.5.1 (C++11 mode). It seems to be resolved by GCC bug 189; in the GCC 4.5 release notes:

G++ now implements DR 176. Previously G++ did not support using the
injected-class-name of a template base class as a type name, and
lookup of the name found the declaration of the template in the
enclosing scope. Now lookup of the name finds the injected-class-name,
which can be used either as a type or as a template, depending on
whether or not the name is followed by a template argument list. As a
result of this change, some code that was previously accepted may be
ill-formed because

  • The injected-class-name is not accessible because it's from a private base, or
  • The injected-class-name cannot be used as an argument for a template template parameter.

In either of these cases, the code can be fixed by adding a
nested-name-specifier to explicitly name the template. The first can
be worked around with -fno-access-control; the second is only rejected
with -pedantic.


My stripped-down testcase with Qt abstracted out:

template <typename T>
struct Base { };

struct Derived : Base<Derived> { // I love the smell of CRTP in the morning
Derived();
};

Derived::Derived() : Base() {};

How to forward C++ base & member initializers through a template constructor?

I suspect you got stuck when trying to pass two packs at the same time. You likely got stuck there because it's the intuitive thing to do. Unfortunately, intuition fails us here because pack deduction from bare function arguments behaves in ways intuition doesn't expect. Trailing packs behave differently, there is difficulty in knowing when the second pack starts and the first ends, and the errors one gets along the way aren't always clear.

In situations like these I like to simply do what the standard library does. std::pair is a type that aggregates two elements. And it allows initializing each one individually (in place) by passing a tuple of parameters to each element (constructor 8). This synergizes with std::forward_as_tuple, a standard function template the does forwarding. You can supply arguments to both the base and the array be doing something similar.

The remaining question is only how to extract the tuple elements. B is easy in C++17's std::make_from_tuple. myArray doesn't have the same benefit, since c-style arrays are irregular types. We can however construct the array with a delegating constructor and an utility std::index_sequence. I'll be using this approach for B as well, thus requiring only C++14 in total.

template <class B, class T>
class fix_array : public B
{
public:
template <class... BRefs, class... DRefs>
fix_array(std::tuple<BRefs...> bArgs, std::tuple<DRefs...> dArgs)
: fix_array(std::move(bArgs), std::make_index_sequence<sizeof...(BRefs)>{}, std::move(dArgs), std::make_index_sequence<sizeof...(DRefs)>{})
{}

private:
T pData[5];

template <class... BRefs, std::size_t... Bidx, class... DRefs, std::size_t... Didx>
fix_array(std::tuple<BRefs...> bArgs, std::index_sequence<Bidx...>, std::tuple<DRefs...> dArgs, std::index_sequence<Didx...>)
: B( std::get<Bidx>(bArgs)... )
, pData{ std::get<Didx>(dArgs)... }
{
std::cerr << "fix_array Base was initialized" << std:: endl;
}
};

The two index_sequence parameters are tag type placeholders that contain the index of every tuple element in order. They are simply there to expand upon and access each tuple element at the correct ordinal. std::get already extract the elements by correct reference type after std::forward_as_tuple has done its thing.

You can see it working in a live example.

How to initialize static member of a template overloaded class

The solution is that you have to repeat the exact template argument list as in the definition.

template<typename T, size_t MaxSubscribers>
template<typename E>
typename E::Args_T EventManager::subscriberMap<T, MaxSubscribers>::events<E, typename std::enable_if<std::is_base_of<Event<T, MaxSubscribers>, E>::value>::type>::list[E::MaxSubscribers];

C++ brace initilization with template parameter

This is due to the definition in the standard. Otherwise, it could be impossible for the compiler to distinguish it from a member function declaration when parsing the code.

2) Through a default member initializer, which is a brace or equals initializer included in the member declaration and is used if the member is omitted from the member initializer list of a constructor.
(Emphasis by me)

https://en.cppreference.com/w/cpp/language/data_members

Accessing base member functions in class derived from template class

When a class template derives from a base class template, the base members are not visible in the derived class template definition. (This makes sense; until you specialize, there is no class, and so there are no members. Explicit specializations can always change the meaning of any given template class.)

In other words, the base template member names are dependent names and not looked up in the first phase of template definition lookup.

There are three ways to get around this. Let's make it concrete with a quick example:

template <typename T> struct Foo
{
int data;
using type = const T &;
void gobble() const;
template <int N> void befuddle();
};

template <typename T> struct X : Foo<T> { /* ... */ };

Now in the context of the derived class template definition, you can...

  1. Qualify the name:

    Foo<T>::data = 10;
    typename Foo<T>::type x;
    Foo<T>::gobble();
    Foo<T>::template befuddle<10>();
  2. Use this:

    this->data = 10;
    this->gobble();
    this->template befuddle<10>();

    (This doesn't work for type names names.)

  3. Use a using declaration:

    using Foo<T>::data;
    using Foo<T>::gobble;
    using type = typename Foo<T>::type;

    data = 10;
    gobble();

    (This doesn't work for template names.)


Update: After your edit, the question is entirely different. Templates don't play a role at all here, since the problem doesn't contain templates, only classes. What's happening is the simple fact that member functions in a derived class hide member functions of the same name in base classes, so the presence of SpecificDerived2::memberFunc hides the base member function. The simple solution is to unhide base members of the same name with a using declaration:

class SpecificDerived2 : public TemplateBase2<float>
{
public:
using TemplateBase2<float>::memberFunc;
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

float memberFunc()
{
return 3.14;
}
};

Template arguments in constructor of non-template class

All template parameters of a constructor template must be deducible (or have default arguments), because there is no syntax for explicitly passing template arguments to a constructor (as you've learned).

There are several possible ways around this:

  1. Provide a constructor-like function template. You're already doing this with newB, there's just no need to force dynamic allocation:

    template <class T>
    B create() { return B(T::GetId()); }
  2. Provide a tag type and parameterise the consturctor by that:

    template <class T>
    struct Tag {};

    class B
    {
    public:
    template <class T>
    B(Tag<T>) : _id(T::GetId()) {}
    };

    //usage:
    B b(Tag<A>());

Iterate over base classes of variadic template class

Here is a way:

struct thru{template<typename... A> thru(A&&...) {}};

struct A { void foo() { std::cout << "A" << std::endl; } };
struct B { void foo() { std::cout << "B" << std::endl; } };
struct C { void foo() { std::cout << "C" << std::endl; } };

template<typename... U>
struct X : public U...
{
void foo() { thru{(U::foo(), 0)...}; }
};

But if you care about the order of calls, watch out for the known gcc bug as discussed here.



Related Topics



Leave a reply



Submit