3

I would like to define a class which inherits from a bunch of classes but which does not hide some specific methods from those classes.

Imagine the following code:

template<typename... Bases>
class SomeClass : public Bases...
{
public: 
  using Bases::DoSomething...;

  void DoSomething(){
    //this is just another overload
  }
};

The problem is now if just one class does not have a member with the name DoSomething I get an error. What I already tried was emulating an "ignore-if-not-defined-using" with a macro and SFINAE but to handle all cases this becomes very big and ugly! Do you have any idea to solve this?

It would be really nice if I could define: "Hey using - ignore missing members".

Here I have some sample code: Godbolt

Bernd
  • 2,113
  • 8
  • 22
  • 1
    Not fully fleshed out, but you could define a wrapper template for the classes you inherit from that adds DoSomething if not present (if you can use c++20 concepts that should be easy) and then `class SomeClass : public Wrapper...` – MikeMB Apr 05 '20 at 09:20
  • Unfortunately it is not easy - you have to handle fields, static methods and instance methods both with and without generic arguments... In addition this not existent / artificial "DoSomething" should be not callable – Bernd Apr 05 '20 at 12:10
  • Put `void DoSomething()` into a helper class, and derive from it alongside `Bases...`. – Igor Tandetnik Apr 05 '20 at 12:36
  • Then I get the error "member found by ambiguous name lookup" see: https://godbolt.org/z/_oQFJ5 – Bernd Apr 05 '20 at 14:34

4 Answers4

5

The problem with Jarod42's approach is that you change what overload resolution looks like - once you make everything a template, then everything is an exact match and you can no longer differentiate between multiple viable candidates:

struct A { void DoSomething(int); };
struct B { void DoSomething(double); };
SomeClass<A, B>().DoSomething(42); // error ambiguous

The only way to preserve overload resolution is to use inheritance.

The key there is to finish what ecatmur started. But what does HasDoSomething look like? The approach in the link only works if there is a single, non-overloaded, non-template. But we can do better. We can use the same mechanism to detect if DoSomething exists that is the one that requires the using to begin with: names from different scopes don't overload.

So, we introduce a new base class which has a DoSomething that will never be for real chosen - and we do that by making our own explicit tag type that we're the only ones that will ever construct. For lack of a better name, I'll name it after my dog, who is a Westie:

struct westie_tag { explicit westie_tag() = default; };
inline constexpr westie_tag westie{};
template <typename T> struct Fallback { void DoSomething(westie_tag, ...); };

And make it variadic for good measure, just to make it least. But doesn't really matter. Now, if we introduce a new type, like:

template <typename T> struct Hybrid : Fallback<T>, T { };

Then we can invoke DoSomething() on the hybrid precisely when T does not have a DoSomething overload - of any kind. That's:

template <typename T, typename=void>
struct HasDoSomething : std::true_type { };
template <typename T>
struct HasDoSomething<T, std::void_t<decltype(std::declval<Hybrid<T>>().DoSomething(westie))>>
    : std::false_type
{ };

Note that usually in these traits, the primary is false and the specialization is true - that's reversed here. The key difference between this answer and ecatmur's is that the fallback's overload must still be invocable somehow - and use that ability to check it - it's just that it's not going to be actually invocable for any type the user will actually use.

Checking this way allows us to correctly detect that:

struct C {
    void DoSomething(int);
    void DoSomething(int, int);
};

does indeed satisfy HasDoSomething.

And then we use the same method that ecatmur showed:

template <typename T>
using pick_base = std::conditional_t<
    HasDoSomething<T>::value,
    T,
    Fallback<T>>;

template<typename... Bases>
class SomeClass : public Fallback<Bases>..., public Bases...
{
public: 
  using pick_base<Bases>::DoSomething...;

  void DoSomething();
};

And this works regardless of what all the Bases's DoSomething overloads look like, and correctly performs overload resolution in the first case I mentioned.

Demo

Barry
  • 286,269
  • 29
  • 621
  • 977
  • Thank you for your nice solution - but I'm still not happy with it. If "A" has a private "DoSomething" it does not compile. This makes using useless for me... (see https://godbolt.org/z/ba5aM6) – Bernd Apr 06 '20 at 20:59
  • 1
    @BerndBaumanns Have `A` friend `SomeClass`. Something's gotta give somewhere. – Barry Apr 06 '20 at 21:34
  • Fast response and good idea! But unfortunately I can't change class "A". – Bernd Apr 07 '20 at 10:16
  • 1
    @BerndBaumanns having the same name with private and public access is going to break using-declarations, and is in general bad practice. Your only way around this is going to be to write a wrapper for `A` that forwards the public member functions in. – ecatmur Apr 07 '20 at 10:29
  • Is there a best practice for handling private / protected members in C++? I usually don't want that a private function hides an inherited public/protected function. – Bernd Apr 07 '20 at 10:48
  • What I have seen a lot is that the name of protected functions get prefixed with something like "doXXX" while the public function remains XXX. So we encode the visibility modifier in the name to avoid these issues. – Bernd Apr 07 '20 at 10:51
2

You might add wrapper which handles basic cases by forwarding instead of using:

template <typename T>
struct Wrapper : T
{
    template <typename ... Ts, typename Base = T>
    auto DoSomething(Ts&&... args) const
    -> decltype(Base::DoSomething(std::forward<Ts>(args)...))
    {
        return Base::DoSomething(std::forward<Ts>(args)...);
    }

    template <typename ... Ts, typename Base = T>
    auto DoSomething(Ts&&... args)
    -> decltype(Base::DoSomething(std::forward<Ts>(args)...))
    {
        return Base::DoSomething(std::forward<Ts>(args)...);
    }

    // You might fix missing noexcept specification
    // You might add missing combination volatile/reference/C-elipsis version.
    // And also special template versions with non deducible template parameter...
};

template <typename... Bases>
class SomeClass : public Wrapper<Bases>...
{
public: 
  using Wrapper<Bases>::DoSomething...; // All wrappers have those methods,
                                        // even if SFINAEd

  void DoSomething(){ /*..*/ }
};

Demo

As Barry noted, there are other drawbacks as overload resolution has changed, making some call ambiguous...

Note: I proposed that solution as I didn't know how to create a correct traits to detect DoSomething presence in all cases (overloads are mainly the problem). Barry solved that, so you have better alternative.

Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • Your solution seems to work but it misses (as you already mentioned in your comments) some cases (static vs. non-static, template parameters...). Before I did something similar - but instead of a wrapper base class I wrote a Macro with a bunch of delegation functions and SFINAE, because with these forwarding methods you don't really need the using. – Bernd Apr 05 '20 at 17:55
  • static/non static is handled. The only part which cannot be fully handled and need to know information with `Bases` is for non deducible template. which is not frequent (`std::get(..)`. and even for these, you might add manually the overload in `Wrapper` (Not ideal indeed). Btw even template function might have issue with `using`. – Jarod42 Apr 05 '20 at 18:54
  • both methods are not static - so they are not handled, or? – Bernd Apr 05 '20 at 21:19
  • I meant wrapper method can call static method, as `Base::f()` can be interpreted as 2 ways, static call, or base class call (`this->Base::f()`) [Demo](https://coliru.stacked-crooked.com/a/053499a196093b85) but indeed, method is no longer static in `SomeClass`/`Wrapper`. – Jarod42 Apr 06 '20 at 06:20
2

How about conditionally using a fallback?

Create non-callable implementations of each method:

template<class>
struct Fallback {
    template<class..., class> void DoSomething();
};

Inherit from Fallback once for each base class:

class SomeClass : private Fallback<Bases>..., public Bases...

Then pull in each method conditionally either from the base class or its respective fallback:

using std::conditional_t<HasDoSomething<Bases>::value, Bases, Fallback<Bases>>::DoSomething...;

Example.

ecatmur
  • 152,476
  • 27
  • 293
  • 366
  • Your traits is not good enough though [Demo](https://godbolt.org/z/D5MF3k), fail with overloads for example. – Jarod42 Apr 06 '20 at 17:59
  • @Jarod42 sure, I was leaving implementation of `HasDoSomething` as an exercise for the reader. There are well-known techniques to deal with overloads, see e.g. https://stackoverflow.com/a/39750372/567292 – ecatmur Apr 07 '20 at 10:26
0

You can implement this without extra base classes so long as you’re willing to use an alias template to name your class. The trick is to separate the template arguments into two packs based on a predicate:

#include<type_traits>

template<class,class> struct cons;  // not defined
template<class ...TT> struct pack;  // not defined

namespace detail {
  template<template<class> class,class,class,class>
  struct sift;
  template<template<class> class P,class ...TT,class ...FF>
  struct sift<P,pack<>,pack<TT...>,pack<FF...>>
  {using type=cons<pack<TT...>,pack<FF...>>;};
  template<template<class> class P,class I,class ...II,
           class ...TT,class ...FF>
  struct sift<P,pack<I,II...>,pack<TT...>,pack<FF...>> :
    sift<P,pack<II...>,
         std::conditional_t<P<I>::value,pack<TT...,I>,pack<TT...>>,
         std::conditional_t<P<I>::value,pack<FF...>,pack<FF...,I>>> {};

  template<class,class=void> struct has_something : std::false_type {};
  template<class T>
  struct has_something<T,decltype(void(&T::DoSomething))> :
    std::true_type {};
}

template<template<class> class P,class ...TT>
using sift_t=typename detail::sift<P,pack<TT...>,pack<>,pack<>>::type;

Then decompose the result and inherit from the individual classes:

template<class> struct C;
template<class ...MM,class ...OO>  // have Method, Others
struct C<cons<pack<MM...>,pack<OO...>>> : MM...,OO... {
  using MM::DoSomething...;
  void DoSomething();
};

template<class T> using has_something=detail::has_something<T>;

template<class ...TT> using C_for=C<sift_t<has_something,TT...>>;

Note that the has_something here supports only non-overloaded methods (per base class) for simplicity; see Barry’s answer for the generalization of that.

Davis Herring
  • 36,443
  • 4
  • 48
  • 76