15

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!

Johannes Schaub - litb
  • 496,577
  • 130
  • 894
  • 1,212
Grzegorz Herman
  • 1,875
  • 11
  • 22
  • I am in a rush, but maybe http://stackoverflow.com/questions/8984013/can-sfinae-detect-private-access-violations can help here, I would not count on gcc 4.6 getting it right though – PlasmaHH Aug 22 '12 at 19:10
  • 1
    access control is a bit tricky here: if you use `sizeof()`, the compiler will check the whole expression, access included -- but then the access is checked **from the context of the expression**, which fails in case of protected constructors; everything other than `sizeof` works only at the level of overload resolution and type inference, so access violations will not trigger SFINAE -- but then, I see no way of doing something with a constructor, as it cannot be passed as a template argument. As for compiler support, I will be happy if **any** of the above accepts the code. – Grzegorz Herman Aug 22 '12 at 19:35

1 Answers1

12

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.

Johannes Schaub - litb
  • 496,577
  • 130
  • 894
  • 1,212
  • Very smart use of ambiguity, I must admit. Let me check if it works in my setting. By the way, how does it deal with private base constructors? – Grzegorz Herman Aug 22 '12 at 19:48
  • @GrzegorzHerman private base constructors are not checked. I must admit :( So it will think that a conversion is possible. – Johannes Schaub - litb Aug 22 '12 at 19:53
  • that's what I thought -- but that is a minor issue for me, the most important is getting it to work for abstract classes :) – Grzegorz Herman Aug 22 '12 at 19:56
  • But an OR conversion is even possible if you try to convert directly to the base (if it is not abstract) and the respective ctor is inaccessible. The case where there is a difference is when you try to do it in an SFINAE context - SFINAE will catch the access violation (and as you noted in your question, only "direct" access violations, not nested in kicked-off instantiations), as opposed to overload resolution. Seen from this POV, both detecting and not detecting the privateness will diverge from the "correct" behavior :) – Johannes Schaub - litb Aug 22 '12 at 20:11
  • 5
    Explanation: type instantiation in SFINAE context is not usable directly, so Johannes' solution exploits function overload resolution, which is not sensitive to access violation or abstract types. If it is possible to construct `T` from `U...` ignoring access violation and abstract types, then both overloads of `check1` become available and are ambiguous, so the `no` overload of `check` is disabled and the constructor is enabled. Otherwise, only one overload of `check` is available, so `no` is enabled and preferred (by its first `int` argument) and the constructor is disabled. Is this right? – ecatmur Aug 22 '12 at 20:52