有什么方法可以检测一个函数是否存在,并且可以在编译时使用?

时间:2021-05-05 20:10:34

Edit: The short answer to my question is that I had a mistaken view of what SFINAE can do and it does not check the function body at all: does sfinae instantiates a function body?

编辑:对我的问题的简短回答是,我对SFINAE可以做什么有错误的看法,它根本不检查函数体:SFINAE实例化一个函数体吗?

I have an issue similar to this one: Is it possible to write a template to check for a function's existence?

我有一个类似的问题:是否可以编写一个模板来检查函数的存在?

The difference is that I want to not only check if the function exists, but I also want to know if it will actually pass SFINAE. Here is an example of what I'm trying to accomplish:

不同之处在于,我不仅想检查函数是否存在,而且我还想知道它是否真的会通过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();
}

Note that in CallFuncs(), both FuncA() and FuncB() will always exist, but they may not compile depending on the type T used in Inter. When I tried to use the answer in the above linked question it seemed to always give me true which I'm guessing is because it's only checking that the function exists, not that it can actually be compiled (though I can't rule out that I didn't screw something up...)

注意,在CallFuncs()中,FuncA()和FuncB()都将始终存在,但它们可能不会根据Inter中使用的T类型进行编译。当我尝试使用上面链接的问题中的答案时,它似乎总是给我正确的答案,我猜这是因为它只是检查函数是否存在,而不是它实际上可以被编译(尽管我不能排除我没有搞砸某件事…)

In order to conditionally call the functions I figure I can use enable_if as such:

为了有条件地调用我认为可以使用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;
};

but I'm not sure if there's any way I can get a boolean value to pass into enable_if. Is there any way I can accomplish this or do I need to fall back to some sort of manually maintained type traits that indicate whether the functions exist?

但是我不确定是否有什么方法可以让布尔值传递给enable_if。有什么方法可以实现这一点吗?或者我需要退回到某种手动维护的类型特征中,以指示函数是否存在?

For what it's worth as far as the available C++11 feature set, I'm using MSVC 2010.

就可用的c++ 11特性集而言,我使用的是MSVC 2010。

edit: To add an important note, in my actual situation the implementation of the class Inter is effectively opaque at the point where I need to determine whether or not Inter::FuncA/FuncB will compile so I can't just bubble up the child types and check for the existence of the function on them.

编辑:添加一个重要的注意,在我实际情况的实现类间实际上是不透明的地方我需要确定是否国米::FuncA / FuncB将编译所以我不能只是泡沫存在的子类型和检查功能。

1 个解决方案

#1


8  

I don't have the time to check this now, but you can add an specialization of Final: template <typename T> struct Final< Inner<T> >; (which also helps ensure that the type is always a Inner. With that you can extract the type used to instantiate Inter.

我现在没有时间检查这个,但是您可以添加一个Final: template struct Final< Inner >;(这也有助于确保类型始终是内部的。这样,您就可以提取用于实例化Inter的类型。

Now the second problem is how to use SFINAE to detect whether a member function exists. I believe this should not be too complex (if you don't need to make this generic):

现在的第二个问题是如何使用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...

您可能可以将其扩展一些,使其更通用一些,但是函数的名称我认为您不可以通用。当然,您可以将其编写为一个宏,生成has_member_# arg并使用&T::: arg。成员的类型可能更容易概括……

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.

或者,因为我不认为这可以通用的,你可以直接使用里面的技巧has_member类型:提供两个callFuncA过载,一个模板化的可选的第二个参数与您想要的签名和违约科技::FuncA将调用转发,省略号是等待。然后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;
};

#1


8  

I don't have the time to check this now, but you can add an specialization of Final: template <typename T> struct Final< Inner<T> >; (which also helps ensure that the type is always a Inner. With that you can extract the type used to instantiate Inter.

我现在没有时间检查这个,但是您可以添加一个Final: template struct Final< Inner >;(这也有助于确保类型始终是内部的。这样,您就可以提取用于实例化Inter的类型。

Now the second problem is how to use SFINAE to detect whether a member function exists. I believe this should not be too complex (if you don't need to make this generic):

现在的第二个问题是如何使用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...

您可能可以将其扩展一些,使其更通用一些,但是函数的名称我认为您不可以通用。当然,您可以将其编写为一个宏,生成has_member_# arg并使用&T::: arg。成员的类型可能更容易概括……

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.

或者,因为我不认为这可以通用的,你可以直接使用里面的技巧has_member类型:提供两个callFuncA过载,一个模板化的可选的第二个参数与您想要的签名和违约科技::FuncA将调用转发,省略号是等待。然后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;
};