7

编辑:对我的问题的简短回答是,我对 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() 将始终存在,但根据 Inter 中使用的类型 T,它们可能无法编译。当我尝试在上面链接的问题中使用答案时,它似乎总是给我真实的,我猜是因为它只是检查函数是否存在,而不是它实际上可以编译(尽管我不能排除我没有搞砸什么...)

为了有条件地调用我认为我可以使用 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::FuncA/FuncB 是否会编译的地方,类 Inter 的实现实际上是不透明的,所以我不能只是冒泡孩子类型并检查它们上是否存在函数。

4

1 回答 1

8

我现在没有时间检查这一点,但您可以添加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) };
};

You might be able to extend this a bit to make it a bit more generic, but the name of the function I don't think you can make generic. Of course, you could write that as a macro that generates has_member_##arg and uses &T:: arg. The type of the member is probably easier to generalize...

Alternatively, since I don't think this can be made generic, you can use the trick inside has_member directly in your type: provide two callFuncA overloads, one templated with the optional second argument with the signature that you want and defaulted to &T::FuncA that forwards the call, the other with ellipsis that is a noop. Then callFuncs would call callFuncA and callFuncB, and SFINAE will dispatch to either the forwarder or the noon and you get your desired behavior.

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;
};
于 2012-06-09T03:31:41.960 回答