#include <cstdint>
#include <iostream>
usingnamespace std;
template <class T=int>
class MyClass{
static_assert(is_same_v<T, std::uint16_t>, ""); //Illustration only for
the must-be solved one
T vs;
//...
};
int main(){
MyClass a; // T defaut type is int
MyClass<uint16_t> b; // not violating
MyClass<long> c; // must not compile nor exit, instead must put 'Go on' out
cout<<"Go on \n";
// ...
}
This caters to situations where the non-null object has member variables,
and also avoids the proliferation of boiler-plate constexpr if constructs if there are many member functions.
#include <iostream>
#include <type_traits>
// the base template is an empty class; does nothing at all (null behaviour, null state)
template < typename T = int, typename = void > struct S // null object
{
constexpr S() {} // do nothing
template < typename U > constexpr S(U) {} // do nothing
constexprvoid foo() const {} // do nothing
void bar() {} // do nothing
// ,,,
constexprfriendvoidoperator<< ( std::ostream&, S ) {} // do nothing
};
// specialisation for integral types (non-empty; has non-trivial behaviour and state)
template < typename T > struct S < T, typename std::enable_if< std::is_integral<T>::value >::type >
{
explicitconstexpr S( T tt = {} ) noexcept : t(tt) {}
// do something
void foo() const { std::cout << "t == " << t << '\n' ; }
void bar() { ++t ; }
// ,,,
T t ; // holds state
friend std::ostream& operator<< ( std::ostream& stm, S<T> s )
{ return stm << "S<integral type>{" << s.t << '}' ; }
};
int main()
{
S<int> si ;
si.bar() ;
si.foo() ;
std::cout << si ;
// this would certainly be compiled (for example, we can't have syntax errors)
// but the code would (should) be optimised away
S<void> sv ; // null object; does nothing
sv.bar() ;
sv.foo() ;
std::cout << sv ;
std::cout << "\n\nthis is at the end of main\n" ;
}
I imagine (a variation on) the pattern @JLBorges demonstrates will be used in library implementations making use of std::is_constant_evaluated in C++20 and later.