Through this is a question asked years ago but I've encountered this recently, so I'll just post it here hope it might help some people.
Using auto
as return type might be another solution. Consider the following code:
template<typename Derived>
class Base
{
public:
auto f()
{
static_cast<Derived*>(this)->f();
}
};
If the derived class doesn't provide a valid overload, then this function becomes recursive, and since auto
requires the final return type, it can never be deduced, thus would be guaranteed to throw an compilation error. For example on MSVC it's something like:
a function that returns 'auto' cannot be used before it is defined
This forces derived class to provide implementation, just like pure virtual function.
The good thing is no extra code is needed, and if the derived class also use auto
as return type then this chain can go as long as required. In some cases it can be convenient and flexible, as in Base
and LevelTwo
in following code, which can return different types when calling the same interface f
. However this chain totally disables direct inheritance of implementation from base class, as in LevelThree
:
template<typename Derived = void>
class Base
{
public:
Base() = default;
~Base() = default;
// interface
auto f()
{
return fImpl();
}
protected:
// implementation chain
auto fImpl()
{
if constexpr (std::is_same_v<Derived, void>)
{
return int(1);
}
else
{
static_cast<Derived*>(this)->fImpl();
}
}
};
template<typename Derived = void>
class LevelTwo : public Base<LevelTwo>
{
public:
LevelTwo() = default;
~LevelTwo() = default;
// inherit interface
using Base<LevelTwo>::f;
protected:
// provide overload
auto fImpl()
{
if constexpr (std::is_same_v<Derived, void>)
{
return float(2);
}
else
{
static_cast<Derived*>(this)->fImpl();
}
}
friend Base;
};
template<typename Derived = void>
class LevelThree : public LevelTwo<LevelThree>
{
public:
LevelThree() = default;
~LevelThree() = default;
using LevelTwo<LevelThree>::f;
protected:
// doesn't provide new implementation, compilation error here
using LevelTwo<LevelThree>::fImpl;
friend LevelTwo;
};
In my case, the derived class I work on also derive from another class which provides extra information needed to determine whether to stop at current class or go for derived class. But in other cases, either break the chain using actual types instead of 'auto', or use some other tricks. But in situations like that maybe virtual function is the best chose.