11

I came across the following situation in one of my projects, where a base class has a function template, which is hidden by a non-templated function in a derived class template. Further down the class hierarchy, the non-templated function is explicitly bringing the function into the scope via a using directive.

Here's a simplified example code:

class Base
{
public:
  template<typename T> const T& get() const;
};

template<typename T> class Derived : public Base
{
private:
  using Base::get;
public:
  const T& get() const;
};

template<typename T> class MoreDerived : public Derived<T>
{
public:
  using Derived<T>::get; // <-- this causes the problem

  const T& call_get() {
    return get();
  }
};

template class MoreDerived<int>;

Godbolt: https://godbolt.org/z/5MQ0VL

The above code fails on GCC and Clang with errors like:

<source>:15:28: error: 'template<class T> const T& Base::get() const' is inaccessible within this context

   15 | template<typename T> class MoreDerived : public Derived<T>

MSVC and ICC accept this code without complaints.

I'm wondering, why the compilers complain about Base::get<T> while there is a public overload Derived<T>::get available?

Removing the private using Base::get from Derived<T> leads to warnings about hiding functions from the base class. So this is unfortunately also not an ideal option.

Without the using Derived<T>::get, calls to get have to be qualified within MoreDerived as it would not be a dependent name otherwise.

Any ideas, what I'm doing wrong here?

pah
  • 313
  • 1
  • 10
  • 2
    Actually, it's never a good idea to make public members of base classes private in derived ones; you violate the "is a" relation ship. What about: `Derived d; d.get(); /* won't compile, but need: */ static_cast(d).get();` – Aconcagua May 22 '19 at 07:58
  • I understand your concern, but making `get` fail to compile for `Derived` is actually a feature of the original code. – pah May 22 '19 at 08:05
  • Is your question "why is it resolving to the non-`const` overload", or is it "why is the `using` declaration pulling in private declarations"? – zneak May 22 '19 at 08:05
  • @zneak: I'm not seeing any non-`const` overloads of `get`? So it's rather the latter. :-) – pah May 22 '19 at 08:08
  • 1
    Is it a requirement to inherit from `Base` publicly? Private inheritance should solve the issue. If `Base` *needs* to be inherited publicly, but the template getter shall be hidden anyway, you simply could make it protected – or you might move it to *another* base class that will be inherited privately. – Aconcagua May 22 '19 at 08:08

2 Answers2

4

I believe what applies here is [namespace.udecl]/17:

In a using-declarator that does not name a constructor, all members of the set of introduced declarations shall be accessible. In a using-declarator that names a constructor, no access check is performed. In particular, if a derived class uses a using-declarator to access a member of a base class, the member name shall be accessible. If the name is that of an overloaded member function, then all functions named shall be accessible. […]

(emphasis mine) in combination with [namespace.udecl]/19:

A synonym created by a using-declaration has the usual accessibility for a member-declaration. […]

The using declaration in MoreDerived creates a synonym for Derived::get which itself is a synonym for the overload set consisting of the member function Derived::get and the member function template Base::get. The latter is not accessible at the point of the using declaration in MoreDerived (because it is private in Derived). Thus, GCC and Clang are correct, this code should not compile. Moving the using declaration in Derived from the private to the public part, for example

template<typename T> class Derived : public Base
{
public:
  using Base::get;
  const T& get() const;
};

resolves the issue…

Michael Kenzel
  • 15,508
  • 2
  • 30
  • 39
  • Not sure this is the whole story - the code compiles just fine if you turn `Base::get` into an ordinary member function (`const int& get() const;`). – lubgr May 22 '19 at 08:05
  • 2
    @lubgr I'd say when you turn `Base::get` into an ordinary member function, then `Derived::get` (which also is an ordinary member function) will hide it and, thus, the problem disappears… – Michael Kenzel May 22 '19 at 08:11
  • @michael-kenzel Thanks for your reply! The reference to the standard helps a lot. So you're saying that the function template `get` is not hidden by `get`? – pah May 22 '19 at 08:16
  • @pah Exactly. An ordinary member function cannot hide a member function template. Functions and function templates are two different kinds of things. Intuitively, consider that a member function template essentially defines a whole set of potential functions itself, so one function alone cannot possibly hide that entire set… – Michael Kenzel May 22 '19 at 08:21
1

Michael Kenzel already explained nicely why your code did fail.

[...] but making get fail to compile for Derived is actually a feature of the original code

Although I cannot encourage such a pattern, as you are violating the "is a" relationship, the following might do the trick for you:

class Base
{
public:
  template<typename T>
  const T& get() const;
};

template<typename T> class Derived : public Base
{
public:
    template<typename U>
    U const& get() const = delete;
    T const& get() const { return Base::get<T>(); }
};

Probably a better option is to simply make the template getter protected instead.

Private inheritance of Base should solve the issue as well, if that's feasible for you; if not, another option might be moving the template getter to a new, separate base class which then will be inherited privately.

Both variants would prevent

Derived<int> d;
static_cast<Base>(d).get<double>();

as well, if this is meaningless anyway.

Aconcagua
  • 24,880
  • 4
  • 34
  • 59
  • In my case, the `Derived::get` is an optimization and does not delegate to `Base::get`. You *could* call `get` through a base pointer, which will give you the slower implementation (and return the same value at the end). Moving forward, I think I will skip the `using` in `MoreDerived` and qualify the calls inside `MoreDerived` instead. – pah May 22 '19 at 08:37
  • @pah Calling the base variant is just an example for implementation, of course you could replace it with *any* other one... The interesting part about is *`delete`ing* the template variant. – Aconcagua May 22 '19 at 08:39
  • Yes, the explicit `delete` of the template variant would be an alternative to skipping the `using` from `MoreDerived`, thanks. – pah May 22 '19 at 08:48
  • @pah You're welcome – anyway, think about the better alternatives. Do you *really* need the template getter to be public in `Base`? – Aconcagua May 22 '19 at 08:52