Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ interface without virtual functions

I was wondering how we can declare an interface in C++ without using virtual functions. After some internet searching I put together this solution:

#include <type_traits>

using namespace std;

// Definition of a type trait to check if a class defines a member function "bool foo(bool)"
template<typename T, typename = void>
struct has_foo : false_type { };

template<typename T>
struct has_foo<T, typename enable_if<is_same<bool, decltype(std::declval<T>().foo(bool()))>::value, void>::type> : true_type { };

// Definition of a type trait to check if a class defines a member function "void bar()"    
template<typename T, typename = void>
struct has_bar : false_type { };

template<typename T>
struct has_bar<T, typename enable_if<is_same<void, decltype(std::declval<T>().bar())>::value, void>::type> : true_type { };

// Class defining the interface
template <typename T>
class Interface{
public:
  Interface(){
    static_assert(has_foo<T>::value == true, "member function foo not implemented");
    static_assert(has_bar<T>::value == true, "member function bar not implemented");
  }
};

// Interface implementation
class Implementation:Interface<Implementation>{
public:
  // If the following member functions are not declared a compilation error is returned by the compiler
  bool foo(bool in){return !in;}
  void bar(){}
};

int main(){}

I'm planning to use this design strategy in a project where I will use static polymorphism only. The C++ standard I will use in the project is C++11.

What do you think are the pros and cons of this approach?

What improvements can be made on the code I proposed?

EDIT 1: I just realised that inheriting from Interface is not needed. This code could also be used:

class Implementation{
  Interface<Implementation> unused;
public:
  bool foo(bool in){return !in;}
  void bar(){}
};

EDIT 2-3: One major difference between the static_assert solution (with or without CRTP) and the standard CRTP is that the CRTP does not guarantee that the derived class implements all the interface members. E.g., the following code compiles correctly:

#include <type_traits>
using namespace std;

template< typename T>
class Interface{
public:
  bool foo(bool in){
    return static_cast<T*>(this)->foo(in);
  }
  void bar(){
    static_cast<T*>(this)->bar();
  }
};

class Implementation: public Interface<Implementation>{
public:
//    bool foo(bool in){return !in;}
//    void bar(){}
};

int main(){}

An error about a missing member function will be returned by the compiler only when the functions foo or bar will be required.

The way I see it, the static_assert solution feels more like an interface declaration than CRTP alone.

like image 288
carlo Avatar asked Jun 01 '17 21:06

carlo


People also ask

What happens if we don't use virtual function in inheritance?

If you don't use virtual functions, you don't understand OOP yet. Because the virtual function is intimately bound with the concept of type, and type is at the core of object-oriented programming, there is no analog to the virtual function in a traditional procedural language.

Why do we need virtual functions in C?

We use virtual functions to ensure that the correct function is called for an object, regardless of the reference type used to call the function. They are basically used to achieve the runtime polymorphism and are declared in the base class by using the virtual keyword before the function.

Does C have virtual functions?

Although C doesn't provide native support for virtual functions, you can emulate virtual functions in C if you attend to all the details.

What is non virtual function?

Non- virtual member functions are resolved statically. That is, the member function is selected statically (at compile-time) based on the type of the pointer (or reference) to the object. In contrast, virtual member functions are resolved dynamically (at run-time).


2 Answers

It appears that you want to implement concepts (lite). You may want to read the article before attempting an implementation.

Absent compiler support, you can partially implement this idea. Your static_assert idea is a known way to express interface requirements.

Consider the Sortable example from the link. You can create a class template Sortable, use static_assert to assert all kind of thinks about the template parameter. You explain to your users that they need to implement a certain cet of methods, and to enforce that set is implemented, they need to make use of Sortable<TheirClass> one way or another.

In order to express, right in a function declaration. the idea that your function requires a Sortable, you will have to resort to something like this:

template <typename Container> 
auto doSomethingWithSortable (Container&) -> std::enable_if<Implements<Container, Sortable>>::type;
like image 172
n. 1.8e9-where's-my-share m. Avatar answered Oct 18 '22 03:10

n. 1.8e9-where's-my-share m.


An common way to implement static polymorphism is to use CRTP.

With this pattern, you define an templated interface class, whose methods forward to the template:

// Interface
template <typename T>
struct base {
    void foo(int arg) {
        static_cast<T*>(this)->do_foo(arg);
    }
};

You implementation the inherits from the base class and implements the methods:

// Implementation
struct derived : base<derived> {
    void do_foo(int arg) {
        std::cout << arg << '\n'
    } 
};

This pattern has the advantage that it looks "feels" a lot like regular runtime polymorphism, and the error messages are generally quite sane. Because all the code is visible to the compiler, everything can be inlined so there's no overhead.

like image 31
Tristan Brindle Avatar answered Oct 18 '22 05:10

Tristan Brindle