1

I have a bunch of useful functions on the object of type T. Here T needs to provide some interface for the functions to work with it. There are several common implementations of the interface. So I made them working as mixins using CRTP.

template<class T>
struct InterfaceImpl {
    using ImplType = InterfaceImpl<T>;
    int foo();
    ...
};

struct MyData : public InterfaceImpl<MyData> {
    ...
};

template<class T>
void aUsefulFunction(T& t) {
    //Working with `t`.
    //This cast is to workaround an accidental hiding of `foo` by MyData.
    static_cast<T::ImplType&>(t).foo();
}

I want the implementation InterfaceImpl (and other implementations also) are provided as it is in some reason. Hiding some of their methods could be very dangerous. Are their any way to enforce no overriding by child classes? I read link on a similar question, but the discussion does not give a satisfactory solution. If there is no reasonable way, I expect that the casting in the above code could give some safety. Or are there any other solution to solve the problem?

jjdoe
  • 13
  • 3
  • You can't override a non-virtual method (you can hide it in the derived class.) The only way to avoid calling any such methods is to either accept a base class instance or (as you are doing) cast to the base class type. I would simplify the above and simply accept any `InterfaceImpl` in the method rather than any `T`. – Nim Jul 03 '17 at 07:35
  • @Nim there are several different implementations of the interface and I want to avoid runtime dispatch. So specifying the argument type is impossible. – jjdoe Jul 03 '17 at 07:45
  • Btw - I think this question has the answer you need: https://stackoverflow.com/questions/4465686/how-to-prevent-a-method-from-being-overridden-in-derived-class, I will vote to close as a duplicate. In case it's not clear, it's the second answer (mark the method `virtual` and `final` - this will cause a compiler error if anyone implements that method in a derived class.) – Nim Jul 03 '17 at 07:46
  • Right, but what you are after is the *same effect*, and it's covered by the answer there. – Nim Jul 03 '17 at 07:49

1 Answers1

3

You can create a traits to see if T has foo and using static_assert on that:

typename <typename T, typename ...Ts>
using foo_type = decltype(std::declval<T>().foo(std::declval<Ts>()...));

template <typename T>
using has_foo = is_detected<foo_type, T>;

template<class T>
struct InterfaceImpl {
    static_assert(!has_foo<T>::value, "T should not have foo method");

    using ImplType = InterfaceImpl<T>;
    int foo();
};

MyData can still hide foo with MyData::foo(int) or similar, but you will have compilation error instead if calling the wrong method.

Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • Out of curiosity, doesn't `virtual` and `final` achieve the same *effect* without requiring the above machinery? – Nim Jul 03 '17 at 07:59
  • @Nim: Not exactly, `virtual void InterfaceImpl::foo() final` can coexist with `void MyData::foo()const` or `void MyData::foo(int = 42)`. In addition polymorphism has a (runtime) cost, whereas we only want to check something at compile time. – Jarod42 Jul 03 '17 at 08:06
  • Unless I am mistaken (and I could very well be), the `virtual` `final` trick catches the same cases (atleast on my compiler it generates a warning if you overload a virtual method...) cv qualifiers I guess would be caught by whichever situation. Now, as for the run-time cost, I guess there could be, but I would imagine that in the situation where we are looking at, the compiler can be smart enough to realize that it's not really calling a virtual function and do the right thing (such as inline if possible..?) May be I'm hoping for too much.. :) – Nim Jul 03 '17 at 08:19