Question

Can I use template parameters to refer to base class member functions?

I'm trying to understand why this code doesn't compile with GCC but compiles with MSVC:

template<class TParams>
class Selector : public TParams::DataSource1, public TParams::DataSource2
{
    using DataSource1 = typename TParams::DataSource1;
    using DataSource2 = typename TParams::DataSource2;

public:
    template<class Fn>
    static void ForEachDataSource(Fn && fn)
    {
        fn.template operator()<DataSource1>();
        fn.template operator()<DataSource2>();
    }

    void Select(int i)
    {
        ForEachDataSource([&, this]<typename T>()
        {
            this->T::Query(i);
        });
    }
};

https://godbolt.org/z/bnv9jcYEe

The class Selector is inherited from multiple classes which provide member function Query. Selector::Select then calls Query from each of the base classes. I've decided to move iteration into a separate function so that I wouldn't need to duplicate logic for each of the base classes. The code above fails to compile with GCC with the following error:

error: 'T' is not a class or namespace
   36 |             this->T::Query(i);

In my understanding T is class DataSource1 in this context and there should be no errors.

Ok, what if we omit this->?

ForEachDataSource([&, this]<typename T>()
{
    T::Query(i);
});

In my understanding, the code is also OK because T refers to one of the base classes, but MSVC doesn't agree with me on this one:

error C2352: 'DataSourceF::Query': a call of a non-static member function requires an object

Explicitly casting this to one of the base classes works in all compilers but requires some detection to support both const and non-const qualified functions.

static_cast<T *>(this)->Query(i); // What if `this` is `const`?

Note that calling Query directly also works in all compilers regardless of used method:

this->DataSource1::Query(i);
DataSource2::Query(i);

Does the standard prohibits using template parameters to refer to base classes when calling their member functions? Which compiler is correct in this case?

 4  83  4
1 Jan 1970

Solution

 3

Yes, you can use template parameters to refer to base class member functions. std::invoke is a helper function for such purposes.

Replace this->T::Query(i); with

std::invoke(&T::Query, this, i);

https://godbolt.org/z/nx5x6KGfK

This is equivalent of the code below that can be compiled by all mentioned compilers.

(this->*&T::Query)(i);
2024-07-22
3CxEZiVlQ