Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 423
Svalorzen Avatar asked May 06 '14 16:05

Svalorzen


People also ask

How do you inherit a function in C++?

Inheritance is a mechanism of reusing and extending existing classes without modifying them, thus producing hierarchical relationships between them. In the main function, object obj accesses function A::f() through its data member B::x with the statement obj. x.f(20) .

How base class member function can be invoked in a derived class?

A virtual function is a member function of a base class that is overridden by a derived class. When you use a pointer or a reference to the base class to refer to a derived class object, you can call a virtual function for that object and have it run the derived class's version of the function.

What is member function explain with example?

A member function of a class is a function that has its definition or its prototype within the class definition like any other variable. It operates on any object of the class of which it is a member, and has access to all the members of a class for that object.


2 Answers

A working solution to your problem as posted at https://ideone.com/mxIVw3 is given below - see also live example.

This problem is in a sense a follow-up of Deduce parent class of inherited method in C++. In my answer, I defined a type trait member_class that extracts a class from a given pointer to member function type. Below we use some more traits to analyse and then synthesize back such a type.

First, member_type extracts the signature, e.g. void (C::*)() gives void():

template <typename M> struct member_type_t { };
template <typename M> using  member_type = typename member_type_t <M>::type;

template <typename T, typename C>
struct member_type_t <T C::*> { using type = T;};

Then, member_class extracts the class, e.g. void (C::*)() gives C:

template<typename>
struct member_class_t;

template<typename M>
using member_class = typename member_class_t <M>::type;

template<typename R, typename C, typename... A>
struct member_class_t <R(C::*)(A...)> { using type = C; };

template<typename R, typename C, typename... A>
struct member_class_t <R(C::*)(A...) const> { using type = C const; };

// ...other qualifier specializations

Finally, member_ptr synthesizes a pointer to member function type given a class and a signature, e.g. C + void() give void (C::*)():

template <typename C, typename S>
struct member_ptr_t;

template <typename C, typename S>
using member_ptr = typename member_ptr_t <C, S>::type;

template <typename C, typename R, typename ...A>
struct member_ptr_t <C, R(A...)> { using type = R (C::*)(A...); };

template <typename C, typename R, typename ...A>
struct member_ptr_t <C const, R(A...)> { using type = R (C::*)(A...) const; };

// ...other qualifier specializations

The two previous traits need more specialization for different qualifiers to be more generic, e.g. const/volatile or ref-qualifiers. There are 12 combinations (or 13 including data members); a complete implementation is here.

The idea is that any qualifiers are transferred by member_class from the pointer-to-member-function type to the class itself. Then member_ptr transfers qualifiers from the class back to the pointer type. While qualifiers are on the class type, one is free to manipulate with standard traits, e.g. add or remove const, lvalue/rvalue references, etc.

Now, here is your is_foo test:

template <typename T>
struct is_foo {
private:
    template<
        typename Z,
        typename M = decltype(&Z::foo),
        typename C = typename std::decay<member_class<M>>::type,
        typename S = member_type<M>
    >
    using pattern = member_ptr<C const, void()>;

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

    template <typename Z> static auto test(Z z) -> decltype(
        helper<pattern<Z>, &Z::foo>(),
        // All other requirements follow..
        std::true_type()
    );

    template <typename> static auto test(...) -> std::false_type;

public:
    enum { value = std::is_same<decltype(test<T>(std::declval<T>())),std::true_type>::value };
};

Given type Z, alias template pattern gets the correct type M of the member pointer with decltype(&Z::foo), extracts its decay'ed class C and signature S, and synthesizes a new pointer-to-member-function type with class C const and signature void(), i.e. void (C::*)() const. This is exactly what you needed: it's the same with your original hard-coded pattern, with the type Z replaced by the correct class C (possibly a base class), as found by decltype.

Graphically:

M = void (Z::*)() const  ->   Z         +   void()
                         ->   Z const   +   void()
                         ->   void (Z::*)() const   ==   M
                         ->   SUCCESS

M = int (Z::*)() const&  ->   Z const&   +   int()
                         ->   Z const    +   void()
                         ->   void (Z::*)() const   !=   M
                         ->   FAILURE

In fact, signature S wasn't needed here, so neither was member_type. But I used it in the process, so I am including it here for completeness. It may be useful in more general cases.

Of course, all this won't work for multiple overloads, because decltype doesn't work in this case.

like image 73
iavr Avatar answered Oct 21 '22 04:10

iavr


If you simply want to check the existence of the interface on a given type T, then there're better ways to do it. Here is one example:

template<typename T>
struct has_foo
{
    template<typename U>
    constexpr static auto sfinae(U *obj) -> decltype(obj->foo(), bool()) { return true; }

    constexpr static auto sfinae(...) -> bool { return false; }

    constexpr static bool value = sfinae(static_cast<T*>(0));
};

Test code:

struct A {
    void foo() {}
};

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

struct C{};

int main() 
{
    std::cout << has_foo<A>::value << std::endl;
    std::cout << has_foo<B>::value << std::endl;
    std::cout << has_foo<C>::value << std::endl;
    std::cout << has_foo<int>::value << std::endl;
    return 0;
}

Output (demo):

1
1
0
0

Hope that helps.

like image 4
Nawaz Avatar answered Oct 21 '22 04:10

Nawaz