Why does public overload conflict with private using directive on some compilers?
Asked Answered
H

2

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?

Harned answered 22/5, 2019 at 7:47 Comment(5)
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<int> d; d.get<double>(); /* won't compile, but need: */ static_cast<Base>(d).get<double>();Simard
I understand your concern, but making get<U> fail to compile for Derived<T> is actually a feature of the original code.Harned
Is your question "why is it resolving to the non-const overload", or is it "why is the using declaration pulling in private declarations"?Stodgy
@zneak: I'm not seeing any non-const overloads of get? So it's rather the latter. :-)Harned
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.Simard
A
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…

Andrewandrewes answered 22/5, 2019 at 8:4 Comment(4)
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;).Postlude
@Postlude 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…Andrewandrewes
@michael-kenzel Thanks for your reply! The reference to the standard helps a lot. So you're saying that the function template get<T> is not hidden by get?Harned
@Harned 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…Andrewandrewes
S
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.

Simard answered 22/5, 2019 at 8:22 Comment(4)
In my case, the Derived<T>::get is an optimization and does not delegate to Base::get<T>. You could call get<T> 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.Harned
@Harned 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 deleteing the template variant.Simard
Yes, the explicit delete of the template variant would be an alternative to skipping the using from MoreDerived, thanks.Harned
@Harned You're welcome – anyway, think about the better alternatives. Do you really need the template getter to be public in Base?Simard

© 2022 - 2024 — McMap. All rights reserved.