Проблема с объявлением дружественной функции с признаками типа в классе

#c #declaration #friend #typetraits

#c #объявление #друг #признаки типов

Вопрос:

Я играю с одноэлементным шаблоном в C и хочу реализовать глобальную функцию, которая завершает построение класса. Я использовал std::is_base_of в этой функции, но это делает невозможным объявление функции в классе.

Вот короткий пример:

 #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
};
 

Приведенный выше код приведет к «недопустимому использованию неполного типа» с использованием gcc или C2139 с использованием MSVC при создании экземпляра первой функции.

Итак, кроме того, чтобы сделать конструктор B::B() общедоступным, есть ли какой-либо возможный способ для меня обойти это?

Ответ №1:

Проблема в том, что во время определения класса класс все еще неполный,

и std::is_base_of требуется полный тип для Derived , иначе у вас есть UB.

Если у вас есть доступ к C 17, вы можете сделать:

 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>();
};
 

ДЕМОНСТРАЦИЯ

Комментарии:

1. Это решение требует наименьшей модификации моего предыдущего кода. Спасибо!

Ответ №2:

Вы можете использовать функтор шаблона для создания объектов целевого класса. Этот функтор должен быть другом целевого класса. Смотрите пример ниже.

 #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;
}
 

Проверьте демонстрацию.

Ответ №3:

Кажется, я могу просто использовать статическую функцию-член (например B::Instance() ) для достижения одноэлементного шаблона и использовать конструктор для завершения определенных функций…

Код:

 #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;
    }
};