Problem on declaring friend function with type traits in a class

Viewed 38

I'm playing around with the singleton pattern in C++, and want to implement an global function that completes the construction of a class. I used std::is_base_of in that function, but that makes it impossible for me to declaring the function in the class.

Here's a short example:

#include <type_traits>
class A {};

template<typename T>
typename std::enable_if_t<std::is_base_of_v<A, T>, T*>
Instance() { return T(); }

template<typename T>
typename std::enable_if_t<!std::is_base_of_v<A, T>, T*>
Instance() { return T(); }

class B : public A {
 protected:
    B();
    friend B* Instance<B>();  // Error
};

The above code will cause "invalid use of incomplete type" using gcc or C2139 using MSVC when instantiating the first function.

So, other than making constructor B::B() to be public, is there any possible way for me to work around with it?

3 Answers

Issue is that during definition of class, the class is still incomplete,

and std::is_base_of required complete type for Derived, else you have UB.

If you have access to C++17, you might do:

template<typename T>
T* Instance() {
    if constexpr (std::is_base_of_v<A, T>) {
        return nullptr; // Your impl
    } else {
        return nullptr; // Your impl
    }
}

class B : public A {
 protected:
    B();
    friend B* Instance<B>();
};

Demo

You can use a template functor to build objects of the target class. This functor must be a friend of the target class. See example below.

#include <type_traits>
#include <tuple>

class A {};

template <typename T>
struct Instance_t
{    
    T* operator () () const
    {
        if constexpr (std::is_base_of_v<A, T>)
        {
            return new T();
        }
        else
        {
            return nullptr;
        }
    }
};
template <typename T>
constexpr Instance_t<T> Instance{};

class B : public A {
 protected:
    B()
    {}
    
    template <typename T> friend struct Instance_t;
};

int main()
{
    auto b=Instance<B>();
    std::ignore=b;
    return 0;
}

Check Demo.

Seems that I can just use static member function (e.g. B::Instance()) to achieve singleton pattern, and use constructor to finish the specific functions...

Code:

#include <type_traits>
class A {
 protected:
    A() {
       // Do something special here
    }
};

class B : public A {
 protected:
    B();

 public:
    static B* Instance() {
        static B* b = new B();
        return b;
    }
};
Related