11

I need to check if a class C has a default constructor, either implicit or custom, and either public, protected or private.

I tried using std::is_default_constructible<C>::value, which returns true if C has a public default constructor (either implicit or custom) but false if C has a protected or private default constructor (seams to be the standard behavior though.)

Is there any way to check if a class has a protected or private default constructor ?

Note (if this may help): the check is performed from a function that is friend of the class C to be checked.


I need to perform this check in order to default-construct objects corresponding to the nullptr pointers of the m_objs tuple, member of a Foo object (partial Foo definition below):

template<class... Objects>
class Foo
{
public:
    Foo(Objects*... objects)
    : m_objs(objects...)
    {
        // User construct a Foo objects passing a pack of pointers
        // some of them are nullptr, some are not.
        // The following call should default-construct objects corresponding
        // to the null pointers of m_objs member tuple:
        objs_ctor<Objects...>();
    }
private:
    template<class Obj, class O1, class ...On>
    void objs_ctor()
    {
        objs_ctor<Obj>(); objs_ctor<O1, On...>();
    }
    template<class Obj>
    typename std::enable_if<std::is_default_constructible<Obj>::value, void>::type
    objs_ctor()
    {
        Obj*& obj = std::get<Obj*>(m_objs);
        if (obj == nullptr)
            obj = new Obj;    // default-construct Obj
    }
    template<class Obj>
    typename std::enable_if<!std::is_default_constructible<Obj>::value, void>::type
    objs_ctor()
    {
        Obj*& obj = std::get<Obj*>(m_objs);
        assert(obj != nullptr);   // terminate if not pre-constructed
    }

private:
    std::tuple<Objects*...>     m_objs;
};

which is intended to be used as:

struct A { };
class B {
    B() = default;

    template <class... Ts>
    friend class Foo;
};

int main() {
    // currently asserts, even though Foo<A,B> is a friend of B
    // and B is default-constructible to its friends
    Foo<A, B> foo(nullptr, nullptr);
}

Example above asserts because std::is_default_constructible<B>::value is false, even though B has a [private] default ctor and Foo<A,B> is friend of B.

shrike
  • 4,449
  • 2
  • 22
  • 38
  • Easy to check for protected - just inherit from it and see if the new guy is default constructible. I am not sure how to do this for private. – SergeyA May 13 '16 at 13:46
  • Have you checked [this post](http://stackoverflow.com/questions/2733377/is-there-a-way-to-test-whether-a-c-class-has-a-default-constructor-other-than)? It is stated that "is_default_constructible returns true even if the default constructor is private or protected" and provides many solutions. It seems like the return value depends on the version of the compiler you are using. Also, if the class that performs the check is a friend to C, you might want to take a look a that [workaround](http://stackoverflow.com/questions/13786482/detect-if-a-default-constructor-exists-at-compile-time). – pandaman1234 May 13 '16 at 13:52
  • @SergeyA: I didn't think about that trick, good solution thanks; still need something for private, though; thanks anyway. – shrike May 13 '16 at 13:56
  • @much_a_chos: I had a look at this thread; afaiu, this is a bug (fixed in the compiler version I use) and OP needs a work around. – shrike May 13 '16 at 13:59
  • @PiotrSkotnicki You want to just post that as an answer? – Barry May 13 '16 at 14:09
  • no, because I don't know how this could be useful – Piotr Skotnicki May 13 '16 at 14:11
  • 1
    could you post the signature of the friend function please? – linuxfever May 13 '16 at 14:13
  • How is the *friend* declared in class A, B or C? – Serge Ballesta May 13 '16 at 15:03
  • @linuxfever: I could but it would be very painful, as the example I provided is a very simplified version of my code; and the problem is not related to friendship: you can easily check that, using a basic class `B` with a private default ctor and a function `void foo()` friend of `B`, `std::is_default_constructible::value` within `foo()` returns `false`. – shrike May 13 '16 at 15:23
  • @Serge: see comment to linuxfever above – shrike May 13 '16 at 15:24

3 Answers3

6

I will present a simplified example to make things easier. Then you can adapt it to your Foos class. The idea is to specialise my templated friend class as follows

#include <iostream>    

// forward declaration
template <class... T>
struct Friend;

struct testing_tag;

// specialisation simply to check if default constructible
template <class T>
struct Friend<T, testing_tag> {

  // sfinae trick has to be nested in the Friend class
  // this candidate will be ignored if X does not have a default constructor
  template <class X, class = decltype(X())>
  static std::true_type test(X*);

  template <class X>
  static std::false_type test(...);

  static constexpr bool value = decltype(test<T>(0))::value;
};

Notice that the std::true_type candidate will always be valid as long as X has a default constructor regardless if it is private, protected or public. Now test it

class default_public {

  template <class... T>
  friend struct Friend;

};

class default_protected {

  template <class... T>
  friend struct Friend;

protected:
  default_protected() {}
};

class default_private {

  template <class... T>
  friend struct Friend;

private:
  default_private() {}

};

class no_default {

public:
  no_default(int x){}
};

// a convenient name to test with
template <class T>
using has_any_default_constructor = Friend<T, testing_tag>;

int main() {
  std::cout << has_any_default_constructor<default_public>::value << std::endl;
  std::cout << has_any_default_constructor<default_protected>::value << std::endl;
  std::cout << has_any_default_constructor<default_private>::value << std::endl;
  std::cout << has_any_default_constructor<no_default>::value << std::endl;
}
linuxfever
  • 3,763
  • 2
  • 19
  • 43
  • just tested your snippet and it works great ! I am going to include this in my real application asap. Thanks a lot ! – shrike May 13 '16 at 17:11
3

The problem is that if a class has neither public, nor protected nor private default constructor, the simple default definition of an instance gives a compilation error, not a run-time exception. So the test is simple: if this expression in a friend function C c;compiles, the class has a default ctor, either public, protected or private.

Just see example code:

#include <iostream>
#include <string>

class Cwith {
private:
    std::string name;
    Cwith(): name("default ctor") {}
    friend void build();
};

class Cwithout {
private:
    std::string name;
    Cwithout(const std::string& name): name(name) {};
    friend void build();
};

void build() {
    Cwith cw;
    std::cout << cw.name << std::endl;
    Cwithout cwo;   // error : class Cwithout has no defaut constructor
    std::cout << cwo.name << std::endl;
}

int main() {
    build();
    return 0;
}

But I could not imagine a run-time test...

Serge Ballesta
  • 143,923
  • 11
  • 122
  • 252
  • Side-comment: is the `friend void build()` count as a prototype for compilation purposes, or do you have to forward-declare the function ahead of the friend declaration? Just wondering. Unfortunately I don't think this will work on classes that you don't have control over without re-compiling. Can you change the .h file to add a `friend` function (But not the library) of a class from another source, and still have it all link OK? Seems very compiler-dependent. – Kevin Anderson May 13 '16 at 14:02
  • Who said anything about runtime? – Barry May 13 '16 at 14:10
  • @Serge: ok, I did not detail the context where I need to perform this check tthough, basically it is performed within a variadic template function whit a parameter pack made of several classes, and some of them has no default constructor but are though legal. – shrike May 13 '16 at 14:10
  • @Barry: in my understanding a test is implicitely run-time – Serge Ballesta May 13 '16 at 14:18
  • @Serge: the test does not need to be performed at runtime, but it should not issue a compiler error; see my edit above. – shrike May 13 '16 at 14:38
  • @Barry: the test does not need to be performed at runtime, but it should not issue a compiler error; see my edit above. – shrike May 13 '16 at 14:38
3

My understanding is that you want to check if your friend can default construct a class. The trick is that you have to have SFINAE inside the scope of the friend function. To do that, you need templates. To have local templates, you need generic lambdas.

We start with an overloader that we can pass multiple lambdas into. There may be a better way to write this:

template <class... Fs>
struct overload {
    void operator()();
};

template <class F, class... Fs>
struct overload<F, Fs...> : F, overload<Fs...> {

    overload(F&& f, Fs&&... fs)
    : F(std::forward<F>(f))
    , overload<Fs...>(std::forward<Fs>(fs)...)
    { }

    using F::operator();
    using overload<Fs...>::operator();
};

template <class... Fs>
overload<std::decay_t<Fs>...> make_overload(Fs&&... fs) {
    return {std::forward<Fs>(fs)...};
}

Then you pass in overloaded, generic, SFINAE-d lambdas (C++14 required). Let's say we have:

struct Q {
    friend void foo();
};

template <class T>
struct tag_t { using type = T; };

template <class T>
constexpr tag_t<T> tag{};

Then we could write:

void foo() {
    auto f = make_overload(
        [](auto x) -> decltype( typename decltype(x)::type(), void() ) { 
            Q q;
            std::cout << "I can do it.\n";
        },
        [](...) {
            std::cout << "What do you want here?\n";
        });

    f(tag<Q>, 0); 
}

If works is a friend, the first overload is viable and preferred - so you get that, which constructs a Q because it can. If works is not a friend, the first overload is not viable and so you get the second overload.

The key is that we test the construction of Q (the typename decltype(x)::type() part) within works() - so it will either be covered by friendship.


So for your specific usage, that would be:

template <class Obj>
objs_ctor() {
    Obj*& obj = std::get<Obj*>(m_objs);

    auto ctor = make_overload(
        [&](auto x) -> decltype( typename decltype(x)::type(), void() ) { 
            if (!obj) {
                obj = new Obj;
            }
        },
        [=](...) {
            assert(obj);
        });

    ctor(tag<Obj>);
}
Barry
  • 286,269
  • 29
  • 621
  • 977
  • A relevant question is whether the `friend` can in fact be a SFINAE'd stati member function of a befriended class. That's for @shrike to answer. – MSalters May 13 '16 at 15:28
  • @MSalters Why? As long as the class template is friended, this works great. – Barry May 13 '16 at 15:30
  • @Barry: I'd been glad to test your solution in my code but my &%#@ msvc14 compiler crashes with fatal error C1001 on decltype in lambda definition... :( – shrike May 13 '16 at 16:57
  • @shrike MSVC doesn't support expression SFINAE, so you're kind of out of luck. – Barry May 13 '16 at 17:12
  • @Barry: OK... gonna think to migrate to gcc one day... anyway thanks for your help. – shrike May 13 '16 at 17:15