Есть ли способ определить, существует ли функция и может ли она использоваться во время компиляции?

Edit: короткий ответ на мой вопрос заключается в том, что у меня было ошибочное представление о том, что SFINAE может делать, и он вообще не проверяет тело функции: создает ли sfinae экземпляр тела функции?

у меня есть проблема, похожая на эту: можно ли написать шаблон для проверки существования функции?

разница в том, что я хочу не только проверить, существует ли функция, но я также хочу знать, будет ли она на самом деле пройти SFINAE. Вот пример того, что я пытаюсь добиться:

struct A
{
    void FuncA() { std::cout << "A::FuncA" << std::endl; }
};

struct B
{
    void FuncA() { std::cout << "B::FuncA" << std::endl; }
    void FuncB() { std::cout << "B::FuncB" << std::endl; }
};

template<typename T>
struct Inter
{
    void FuncA() { t.FuncA(); }
    void FuncB() { t.FuncB(); }

    T t;
};

// Always takes some sort of Inter<T>.
template<typename InterType>
struct Final
{
    void CallFuncs()
    {
        // if( t.FuncA() exists and can be called )
            t.FuncA();

        // if( t.FuncB() exists and can be called )
            t.FuncB();
    }

    InterType t;
};

void DoEverything()
{
    Final<Inter<A>> finalA;
    Final<Inter<B>> finalB;

    finalA.CallFuncs();
    finalB.CallFuncs();
}

обратите внимание, что в CallFuncs () всегда будут существовать как FuncA (), так и FuncB (), но они могут не компилироваться в зависимости от типа T, используемого в Inter. Когда я попытался использовать ответ в приведенном выше связанном вопросе, он, казалось, всегда давал мне истину, которую я предполагаю, потому что это только проверка того, что функция существует, а не то, что она действительно может быть скомпилирована (хотя я не могу исключить, что я не завинтил что-то вверх...)

для условного вызова функций, которые я считаю, я могу использовать enable_if как таковой:

template<typename InterType>
typename std::enable_if< ! /* how to determine if FuncA can be called? */>::type TryCallFuncA( InterType& i )
{
}
template<typename InterType>
typename std::enable_if</* how to determine if FuncA can be called? */>::type TryCallFuncA( InterType& i )
{
    i.FuncA();
}

template<typename InterType>
typename std::enable_if< ! /* how to determine if FuncB can be called? */>::type TryCallFuncB( InterType& i )
{
}
template<typename InterType>
typename std::enable_if</* how to determine if FuncB can be called? */>::type TryCallFuncB( InterType& i )
{
    i.FuncB();
}

template<typename InterType>
struct Final
{
    void CallFuncs()
    {
        TryCallFuncA(t);
        TryCallFuncB(t);
    }

    InterType t;
};

но я не уверен, есть ли способ получить логическое значение для передачи в enable_if. Есть ли способ выполнить это или мне нужно вернуться к каким-то поддерживаемым вручную признакам типа, которые указывают, существуют ли функции?

для чего это стоит, насколько доступен набор функций C++11, я использую MSVC 2010.

редактировать: чтобы добавить важное замечание, в моей фактической ситуации реализация класса Inter эффективно непрозрачна в тот момент, когда мне нужно определить, будет ли Inter::FuncA/FuncB компилироваться, поэтому я не могу просто пузырить дочерние типы и проверять наличие функции на них.

1 ответов


у меня нет времени, чтобы проверить это сейчас, но вы можете добавить специализацию Final: template <typename T> struct Final< Inner<T> >; (что также помогает гарантировать, что тип всегда является Inner. С помощью этого вы можете извлечь тип, используемый для создания экземпляра Inter.

теперь вторая проблема заключается в том, как использовать SFINAE для определения, существует ли функция-член. Я считаю, что это не должно быть слишком сложным (если вам не нужно делать это общим):

// Find out whether U has `void f()` member
template <typename U>
struct has_member_f {
    typedef char yes;
    struct no { char _[2]; };
    template<typename T, void (T::*)() = &T::f>
    static yes impl( T* );
    static no  impl(...);

    enum { value = sizeof( impl( static_cast<U*>(0) ) ) == sizeof(yes) };
};

возможно, вы сможете немного расширить это сделайте его немного более общим, но имя функции я не думаю, что вы можете сделать общим. Конечно, вы можете написать это как макрос, который генерирует has_member_##arg и использует &T:: arg. Тип члена, вероятно, легче обобщить...

альтернативно, так как я не думаю, что это можно сделать общим, вы можете использовать трюк внутри has_member непосредственно в вашем типе: предоставьте два callFuncA overloads, один шаблонный с необязательным вторым аргументом с сигнатурой, которую вы хотите и по умолчанию &T::FuncA это перенаправляет вызов, другой с многоточием, который является noop. Тогда callFuncs назвали бы callFuncA и callFuncB, и SFINAE отправит либо экспедитору, либо в полдень, и вы получите желаемое поведение.

template<typename T>
struct Final< Inter<T> >
{
    template <typename U, void (U::*)() = &U::FuncA>
    void callFuncA( Inter<T>* x ) {
        x.FuncA();
    }
    void callFuncA(...) {}

    void CallFuncs() {
        callFuncA(&t);                 // Cannot pass nonPOD types through ...
        // Similarly TryCallFuncB(t);
    }
    Inter<T> t;
};