7

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.

Community
  • 1
  • 1
Screndib
  • 1,024
  • 11
  • 23

1 Answers1

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.

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;
};
David Rodríguez - dribeas
  • 204,818
  • 23
  • 294
  • 489
  • If I remember correctly, there is an issue with default arguments and functions templates in C++03 that would make this solution only available in C++11. Could you confirm/infirm ? – Matthieu M. Jun 09 '12 at 10:39
  • @MatthieuM.: Correct, this is a C++11 only solution. In C++ you cannot provide the default argument to the function template. This is a really nice feature that has flown under the radar in the new standard and required in many of the cases where the standard mandates that a particular function *shall not participate in overload resolution unless X is true.* (i.e. mandated SFINAE). – David Rodríguez - dribeas Jun 09 '12 at 11:26
  • Unfortunately that's not supported in VS (2010 or 2012). For the purposes of understanding the solution, the default argument &U::FuncA is not only checked that it exists but also that if it is called that it will compile correctly (for example the type provided to Inter could itself be a template class (e.g. struct A) and the implementation of FuncA/FuncB depends on that type)? – Screndib Jun 09 '12 at 18:17
  • @Screndib: The detection is based on SFINAE, and that can only check for existence. If the member is not accessible (it is private) then it will not be a *substitution* error, and the compiler will fail with an error. Now the trick in this case is that you don't want to check `Inter`, but rather `X` (because in your case `Inter` always has both functions even if they fail to compile. Now if `X` is itself an instantiation of a template or a plain class does not really matter, once a template is instantiated it becomes a type, just like (or at least very close to) a hand rolled class. – David Rodríguez - dribeas Jun 09 '12 at 21:19
  • ... that is, if your type is `Final>>` this solution extracts the `A` block and tests there. The same trick can be applied recursively, that is, you could add an extra level to remove the `A` template, extract `X` and verify whether `X::f` exists or not. – David Rodríguez - dribeas Jun 09 '12 at 21:22
  • I fear that my example may have introduced some unintended assumptions (e.g. my actual situation has arbitrary function implementations, not just FuncA all the way down). Given the example of Final>>, lets say X is set to an iterator type, and A::FuncA() { --someX; }. In this case a forward iterator would fail to compile because operator-- is not defined. So what I want is to say if Inter::FuncA() compiles then great, use it, otherwise just don't bother making the call. As far as the contents of the Inter::FuncA, in the example it always calls T::FuncA, but... – Screndib Jun 09 '12 at 22:30
  • ...I also need to handle the case where maybe someone specializes Inter and defines the function as Inter::FuncA() { DoWhoKnowsWhatThatMayOrMayNotCompile(); } – Screndib Jun 09 '12 at 22:31
  • @Screndib: I am not sure I follow the problem but you should ask about the problem that you need solved, rather than asking about a particular approach: i.e. instead of asking how to apply SFINAE to enable/disable a call, describe whet you really need done. – David Rodríguez - dribeas Jun 10 '12 at 02:53
  • @DavidRodríguez-dribeas how this 'feature' of the language is called: `template static yes impl( T* );` ? Just wondering how C++ can resolve `T*` (which is normally a pointer to object) as function pointer?(not sure if it really is a function pointer) – andrgolubev Mar 18 '17 at 19:14
  • @andrgolubev: The caller passes `(U*)0`, which is a pointer to `U`. The function deduces `T` to be `U`, and because there's no deduction or value for the second arguments uses that `T` to substitute on the default argument for the template. The (unnamed) second argument becomes: `void (U::*)() = &U::f` which will succeed if `U` has the appropriate member of fail otherwise. In the failure case, substitution of `U` into `T` is not an error, and the overload is removed from the set of candidates, leaving only the otherwise worse candidate taking the ellipsis. – David Rodríguez - dribeas Mar 20 '17 at 11:50
  • @andrgolubev: Note that `T` in that template is *not* a function template, but a pointer to a type that should have a member `f`. But this is just this case. In other constructs, the compiler would not have any issue resolving `T*` to be a function pointer type, with `T` having the type of the signature --again, not in this use case, but that's just fine, a function pointer *is* a pointer to a type that represents the function. – David Rodríguez - dribeas Mar 20 '17 at 11:53