Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there any way to detect whether a function exists and can be used at compile time?

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?

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:

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...)

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

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?

For what it's worth as far as the available C++11 feature set, I'm using 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.

like image 897
Screndib Avatar asked Jun 09 '12 02:06

Screndib


1 Answers

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.

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):

// 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;
};
like image 187
David Rodríguez - dribeas Avatar answered Nov 15 '22 11:11

David Rodríguez - dribeas