Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

detecting protected constructors of (possibly abstract) base class

I am experimenting with the new features of C++11. In my setup I would really love to use inheriting constructors, but unfortunately no compiler implements those yet. Therefore I am trying to simulate the same behaviour. I can write something like this:

template <class T>
class Wrapper : public T {
    public:
    template <typename... As>
    Wrapper(As && ... as) : T { std::forward<As>(as)... } { }
    // ... nice additions to T ...
};

This works... most of the time. Sometimes the code using the Wrapper class(es) must use SFINAE to detect how such a Wrapper<T> can be constructed. There is however the following issue: as far as overload resolution is concerned, the constructor of Wrapper<T> will accept any arguments -- but then compilation fails (and this is not covered by SFINAE) if the type T cannot be constructed using those.

I was trying to conditionally enable the different instantiations of the constructor template using enable_if

    template <typename... As, typename std::enable_if<std::is_constructible<T, As && ...>::value, int>::type = 0>
    Wrapper(As && ... as) // ...

which works fine as long as:

  • the appropriate constructor of T is public
  • T is not abstract

My question is: how to get rid of the above two constraints?

I tried to overcome the first by checking (using SFINAE and sizeof()) whether the expression new T(std::declval<As &&>()...) is well-formed within Wrapper<T>. But this, of course, does not work, because the only way a derived class can use its base's protected constructor is in the member initialization list.

For the second one, I have no idea whatsoever -- and it is the one I need more, because sometimes it is the Wrapper which implements the abstract functions of T, making it a complete type.

I want a solution which:

  • is correct according to the standard
  • works in any of gcc-4.6.*, gcc-4.7.* or clang-3.*

Thanks!

like image 960
Grzegorz Herman Avatar asked Aug 22 '12 19:08

Grzegorz Herman


1 Answers

This appears to work fine on my local GCC (4.7, courtesy of rubenvb). GCC on ideone prints several "implemented" compiler internal errors though.

I had to make the "implementation details" of the Experiment class public, because for some reasons (which smells like a bug), my version of GCC complains about them being private, even though only the class itself uses it.

#include <utility>

template<typename T, typename Ignored>
struct Ignore { typedef T type; };

struct EatAll {
  template<typename ...T>
  EatAll(T&&...) {}
};

template<typename T>
struct Experiment : T {
public:
  typedef char yes[1];
  typedef char no[2];

  static void check1(T const&);
  static void check1(EatAll);

  // if this SFINAE fails, T accepts it
  template<typename ...U>
  static auto check(int, U&&...u)
    -> typename Ignore<no&, 
        decltype(Experiment::check1({std::forward<U>(u)...}))>::type;

  template<typename ...U>
  static yes &check(long, U&&...);

public:
  void f() {}
  template<typename ...U, 
           typename std::enable_if<
             std::is_same<decltype(Experiment::check(0, std::declval<U>()...)),
                          yes&>::value, int>::type = 0>
  Experiment(U &&...u):T{ std::forward<U>(u)... }
  {}
};

// TEST

struct AbstractBase {
  protected:
    AbstractBase(int, float);
    virtual void f() = 0;
};

struct Annoyer { Annoyer(int); };

void x(Experiment<AbstractBase>);
void x(Annoyer);

int main() {
  x({42});
  x({42, 43.f});
}

Update: The code also works on Clang.

like image 101
Johannes Schaub - litb Avatar answered Oct 10 '22 15:10

Johannes Schaub - litb