Ошибка при попытке вызова метода неполного типа

Есть некоторая библиотека, которая предоставляет примерно такой интерфейс:

namespace thirparty
{
    template<typename T>
    class SM
    {
        static_assert(
            requires(T sm) { { sm.operator()() }; },
        "no configurable"
            );
    public:
        constexpr SM() {}
        constexpr explicit SM(T& s) {}
    };

    template<typename... Ts>
    constexpr auto some_fun(Ts... ts)
    {
        return true;
    }
}

Я в своих классах добавляю экземпляр thirparty::SM sm следующим образом:

template <typename T>
struct Obj1
{
    int fun() const { return 42; };

    struct inner
    {
        double a{ 3.14 };
        int g() const { return 7; }
        auto operator()() const
        {
            return thirparty::some_fun(
                g()
                , a
                // , &Obj1::fun
            );
        }
    };
    thirparty::SM<inner> sm{}; // необходимый экземпляр
};

В такой реализации все работает. Но есть проблема: нет прямого доступа к членам класса Obj1 при формировании параметров вызова thirparty::some_fun().

При попытке решить эту задачу, я пришел к такому решению:

template<typename T>
struct SMAdapter
{
    T* obj;
    auto operator()() const
    {
        return std::apply(
            [](auto&&... args)
            {
                return thirparty::some_fun(args...);
            },
            obj->args() // error C2027: use of undefined type 'Obj<int>'
        );
    }
};

template<typename T>
struct Obj
{
    using Self = Obj<T>;

    int fun() const { return 42; }
    double a{ 3.14 };

    auto args()
    {
        return std::tuple
        {
            fun()
            , a
        };
    }

    SMAdapter<Self> sm_adapter{ this };
    thirparty::SM<decltype(sm_adapter)> sm{ sm_adapter }; // необходимый экземпляр
};

В данном случае аргументы для thirparty::some_fun() формируются с прямым доступом к членам Obj. Но этот код не компилируется с ошибкой:

error C2027: use of undefined type 'Obj'

при создании экземпляра Obj<int> obj{};

Как я понял, проблема в том, что идет обращение к члену объявленного класса Obj до его реализации. Есть ли способ обойти эту проблему?

Или, возможно, существует другое решение?

UPD: полный пример

#pragma once

#include <tuple>

/*===========================================================================*/
namespace thirparty
{
    template<typename T>
    class SM
    {
        static_assert(
            requires(T sm) { { sm.operator()() }; },
        "no configurable"
            );
    public:
        constexpr SM() {}
        constexpr explicit SM(T& s) {}
    };

    template<typename... Ts>
    constexpr auto some_fun(Ts... ts)
    {
        return true;
    }
}


/*===========================================================================*/
template <typename T>
struct Obj1
{
    int fun() const { return 42; };

    struct inner
    {
        double a{ 3.14 };
        int g() const { return 7; }
        auto operator()() const
        {
            return thirparty::some_fun(
                g()
                , a
                // , &Obj1::fun
            );
        }
    };
    thirparty::SM<inner> sm{};
};


/*===========================================================================*/
template<typename T>
struct SMAdapter
{
    T* obj;
    auto operator()() const
    {
        return std::apply(
            [](auto&&... args)
            {
                return thirparty::some_fun(args...);
            },
            obj->args()
        );
    }
};

template<typename T>
struct Obj
{
    using Self = Obj<T>;

    int fun() const { return 42; }
    double a{ 3.14 };

    auto args()
    {
        return std::tuple
        {
            fun()
            , a
        };
    }

    SMAdapter<Self> sm_adapter{ this };
    thirparty::SM<decltype(sm_adapter)> sm{ sm_adapter };
};

void test_2()
{
    Obj1<int> obj1{};
    Obj<int> obj{};
}

UPD2: Возможно, я запутанно описал проблему, описать проблему простыми словами - не лучшая моя сторона. Создал пример в godbolt. Ошибка возникает, если снять комментарий со строки Obj<int> obj{}; в функции main(). Есть ли возможность доработать этот пример, чтобы он компилировался?


Ответы (1 шт):

Автор решения: user7860670

Вот минимальный пример:

#include <utility>

template<typename x_WithMethod>
struct Foo
{
    // error C2027: use of undefined type 'Bar'
    decltype(::std::declval<x_WithMethod>().method()) field;
};

struct Bar
{
    int method() { return 0; }
    Foo<Bar> bar;
};

online compiler

Это происходит потому, что при инстанцировании Foo<Bar> тип Bar еще является неполным (incomplete), и нельзя пытаться вызывать его методы в объявлениях Foo. В коде из вопроса в объявлении thirparty::SM<decltype(sm_adapter)> sm{ sm_adapter }; инстанцируется кдасс SM и его static_assert дергает sm.operator(), который дергает SMAdapter::operator(), возвращаемые тип которого зависит от obj->args(), который дергает Obj<int>::args(), в то время как тип Obj<int> является неполным.

¿Что же делать? Можно добавить требование полноты типа, который передается шаблонным параметром в SM или SMAdapter или можно разорвать цепочку зависимостей на неполный тип, например явно задав возвращаемый тип SMAdapter::operator() как bool или добавив трейт для Obj, который уже будет полным при инстанцировании SM или просто доп типы вместо вывода возвращаемого значения метода.

template<typename x_WithType>
struct Foo
{
    typename x_WithType::Type field;
};

struct Bar
{
    using Type = int;
    Foo<Bar> bar;
};
→ Ссылка