Is is possible to declare types where it only allows that class and not any subclasses (I know this violates the Liskov substitution principle but I still want to know if there's a way to do it.)
For example,
#include <iostream>
struct A {};
struct B : A {};
void fn(/*I want this to be only A, not subclasses*/A arg) {
// do stuff with arg
std::cout << "fn called";
}
int main() {
A a;
fn(a);
B b;
fn(b); // should raise compile-time error here
}
I want fn(b)
to give a compile-time error.
Link to code: https://wandbox.org/permlink/AiLkHwp5rg7AD7gf
There are a lot of ways to approach this, so let's compare them:
Constraints (since C++20)
Diagnostic
This option works because
auto
is deduced toB
here, which is not the same asA
. The constraint doesn't care thatB
is convertible toA
(althoughstd::convertible_to
would).Pros and Cons
std::enable_if
and SFINAE (since C++11)Diagnostic
This implementation is basically the same as the C++20 version, we just do it via SFINAE instead of constraints.
Pros and Cons
std::enable_if
, other compilers will produce much worse outputDeleted functions (since C++11)
Diagnostic
We are allowed to declare any function as deleted, where calling it makes the program ill-formed. This solution works because
fn(T)
wins in overload resolution, as no implicit conversion fromB
toT
is required, only fromB
toA
.Pros and Cons
= delete
on arbitrary functions is surprising, it's not a well-known featurestatic_assert
(since C++11)Diagnostic
This is a very simple, but effective solution. We check whether the type we were given is actually
A
. No implicit conversions fromB
toA
would be considered.Pros and Cons
Conclusion
The quality of errors is alright for every solution, at least when using clang. Which solution you prefer depends in part on what requirements you have, and what version of C++ you want to be compatible with. However, personal preference also plays a role here, since none of the solutions are better than others in every regard.