4

I'm making a base class, which has some methods, which are used in derived classes. This base class is something like an abstract class in the sense that apart from (protected) methods, it defines the interface (i.e. public) methods which must be implemented in derived classes. But it's not intended to be used as a polymorphic base, instead its derivatives will be used as template parameters for some other functions/functors, which would call the interface methods.

Given the above, I could use the usual way of defining abstract classes like using pure virtual functions, but there's a problem with this: the resulting derivative classes are required to have standard layout. Thus no virtual functions allowed. But still there'll be many derivatives, which will not be used until some later time, and I'd like to make the compiler check that all the methods required are implemented with the correct signature (e.g. int Derived::f(double) instead of int Derived::f(float) is not allowed).

What would be a good way to do this, taking into account the requirement of standard layout?

Ruslan
  • 18,162
  • 8
  • 67
  • 136
  • 2
    Perhaps the Curiously Recurring Template Pattern helps. So your base class calls derived-class methods, but the resolution is done at compile time. – Toby Speight Sep 01 '15 at 17:25
  • Sounds like concepts. See http://www.boost.org/doc/libs/1_58_0/libs/concept_check/concept_check.htm –  Sep 01 '15 at 17:32
  • @TobySpeight why don't you use "answer" box to write your answer but use a comment? (@DieterLücking) – Alex Lop. Sep 01 '15 at 17:36
  • @Alex - because it's speculation rather than a complete answer. I didn't have time to actually try it myself. – Toby Speight Sep 01 '15 at 17:43
  • @TobySpeight how can the base class check that the method isn't just compatible, but is exactly as required? (see the example in the question: `int Derived::f(double)` instead of `int Derived::f(float)` is not allowed). – Ruslan Sep 01 '15 at 18:11

1 Answers1

2

Here's an implementation of the CRTP pattern with a static_assert inside the interface dispatching routine:

#include <iostream>
#include <type_traits>

template<class Derived>
class enable_down_cast
{
        typedef enable_down_cast Base;
public:
        // casting "down" the inheritance hierarchy
        Derived const* self() const { return static_cast<Derived const*>(this); }
        Derived*       self()       { return static_cast<Derived*      >(this); }
protected:
        // disable deletion of Derived* through Base*
        // enable deletion of Base* through Derived*
        ~enable_down_cast() = default; 
};

template<class FX>
class FooInterface
:
    public enable_down_cast< FX >
{
    using enable_down_cast< FX >::self; // dependent name now in scope
public:
    int foo(double d) 
    {
        static_assert(std::is_same<decltype(self()->do_foo(d)), int>::value, "");
        return self()->do_foo(d); 
    }
protected:
    // disable deletion of Derived* through Base*
    // enable deletion of Base* through Derived*
    ~FooInterface() = default;
};

Note that the above static_assert only fires if the return types of the interface and the implementation don't match. But you can decorate this code with any type trait you wish, and there are plenty of Q&As here on SO that write type traits to check for an exact function signature match between interface and implementation.

class GoodFooImpl
:
    public FooInterface< GoodFooImpl > 
{
private:
    friend class FooInterface< GoodFooImpl > ;
    int do_foo(double) { std::cout << "GoodFooImpl\n"; return 0; }
};

class BadFooImpl
:
    public FooInterface< BadFooImpl > 
{
private:
    friend class FooInterface< BadFooImpl >;
    char do_foo(double) { std::cout << "BadFooImpl\n"; return 0; }
};

int main()
{
    GoodFooImpl f1;         
    BadFooImpl f2;

    static_assert(std::is_standard_layout<GoodFooImpl>::value, "");
    static_assert(std::is_standard_layout<BadFooImpl>::value, "");

    f1.foo(0.0);
    f2.foo(0.0); // ERROR, static_assert fails, char != int
}

Live Example on Coliru. Note that the derived class is indeed standard layout.

Community
  • 1
  • 1
TemplateRex
  • 69,038
  • 19
  • 164
  • 304