Search code examples
c++c++11matchsfinaemember-pointers

Match type of inherited member functions


I have the following snipped of code, which does not compile.

#include <iostream>

struct A {
    void foo() {}
};

struct B : public A {
    using A::foo;
};

template<typename U, U> struct helper{};

int main() {
    helper<void (A::*)(), &A::foo> compiles;
    helper<void (B::*)(), &B::foo> does_not_compile;

    return 0;
}

It does not compile since &B::foo resolves to &A::foo, and thus it cannot match the proposed type void (B::*)(). Since this is part of a SFINAE template that I am using to check for a very specific interface (I'm forcing specific argument types and output types), I would like for this to work independently of inheritances, while keeping the check readable.

What I tried includes:

  • Casting the second part of the argument:

    helper<void (B::*)(), (void (B::*)())&B::foo> does_not_compile;

    This unfortunately does not help as the second part is now not recognized as a constant expression, and fails.

  • I've tried assigning the reference to a variable, in order to check that.

    constexpr void (B::* p)() = &B::foo; helper<void (B::* const)(), p> half_compiles;

    This code is accepted by clang 3.4, but g++ 4.8.1 rejects it, and I have no idea on who's right.

Any ideas?

EDIT: Since many comments are asking for a more specific version of the problem, I'll write it here:

What I'm looking for is a way to explicitly check that a class respects a specific interface. This check will be used to verify input arguments in templated functions, so that they respect the contract that those functions require, so that compilation stops beforehand in case the class and a function are not compatible (i.e. type traits kind of checking).

Thus, I need to be able to verify return type, argument type and number, constness and so on of each member function that I request. The initial question was the checking part of the bigger template that I'm using to verify matches.


Solution

  • Here's a simple class that passes your tests (and doesn't require a dozen of specializations :) ). It also works when foo is overloaded. The signature that you wish to check can also be a template parameter (that's a good thing, right?).

    #include <type_traits>
    
    template <typename T>
    struct is_foo {
        template<typename U>
        static auto check(int) ->
        decltype( static_cast< void (U::*)() const >(&U::foo), std::true_type() );
        //                     ^^^^^^^^^^^^^^^^^^^
        //                     the desired signature goes here
    
        template<typename>
        static std::false_type check(...);
    
        static constexpr bool value = decltype(check<T>(0))::value;
    };
    

    Live example here.

    EDIT :

    We have two overloads of check. Both can take a integer literal as a parameter and because the second one has an ellipsis in parameter list it'll never be the best viable in overload resolution when both overloads are viable (elipsis-conversion-sequence is worse than any other conversion sequence). This lets us unambiguously initialize the value member of the trait class later.

    The second overload is only selected when the first one is discarded from overload set. That happens when template argument substitution fails and is not an error (SFINAE).

    It's the funky expression on the left side of comma operator inside decltype that makes it happen. It can be ill-formed when

    1. the sub-expression &U::foo is ill-formed, which can happen when

      • U is not a class type, or
      • U::foo is inaccesible, or
      • there is no U::foo
    2. the resulting member pointer cannot be static_cast to the target type

    Note that looking up &U::foo doesn't fail when U::foo itself would be ambiguous. This is guaranteed in certain context listed in C++ standard under 13.4 (Address of overloaded function, [over.over]). One such context is explicit type conversion (static_cast in this case).

    The expression also makes use of the fact that T B::* is convertible to T D::* where D is a class derived from B (but not the other way around). This way there's no need for deducing the class type like in iavr's answer.

    value member is then initialized with value of either true_type or false_type.


    There's a potential problem with this solution, though. Consider:

    struct X {
        void foo() const;
    };
    
    struct Y : X {
        int foo();   // hides X::foo
    };
    

    Now is_foo<Y>::value will give false, because name lookup for foo will stop when it encounters Y::foo. If that's not your desired behaviour, consider passing the class in which you wish to perform lookup as a template parameter of is_foo and use it in place of &U::foo.

    Hope that helps.