License : Creative Commons Attribution 4.0 International (CC BY-NC-SA 4.0)
Copyright :
Hervé Frezza-Buet,
CentraleSupelec
Last modified : April 19, 2024 10:22
Link to the source : sfinae.md
/*
g++ -o test example-sfinae-001.cpp -std=c++20 -lpthread
*/
#include <iostream>
// Let us define a clear_if_clear_available<PROCESS, DUMMY_TYPE>
// template. The dummy type can be omitted, since its default value is
// defined to be void.
//
// clear_if_clear_available<PROCESS> cica;
//
// ... declares an instance of the following class, where operator()
// do nothing, and where the DUMMY_TYPE is void... and useless.
template<typename PROCESS, typename = void>
struct clear_if_clear_available {
void operator()(const PROCESS& d) {}
};
// The previous can change if we specify the template. Indeed, if we define
//
// template<typename PROCESS>
// struct clear_if_clear_available<PROCESS, int> {...};
//
// ... the compiler will use this specification if we only declare
//
// clear_if_clear_available<PROCESS> cica;
//
// ... as previously. The trick is to define such a specification,
// allowing a syntax that overshadoes the default declaration (with
// its default second type variable). But the point is that instead of
// int, we will use the template compiling rules to generate a type
// from PROCESS. It if fails, only the general class defined previously
// is avaiable. If it succeeds, the second one overshadoes the
// previous class.
//
// std::declval<PROCESS>() is a "syntaxical" instance of the type
// PROCESS. So std::declval<PROCESS>().clear() is the syntactical
// expression invoking the method clear of the class PROCESS. Its type
// is decltype(std::declval<PROCESS>().clear()). The compiler can
// infer it only from its type checking mechanism. If clear is not
// defined for the class used as the PROCESS type, you get a syntax
// error... which is not a failure since the compiler only aborts the
// definition of this specification of clear_if_clear_available.
template<typename PROCESS>
struct clear_if_clear_available<PROCESS, decltype(std::declval<PROCESS>().clear())> {
void operator()(const PROCESS& d) {d.clear();}
};
// Ok, now we have a template, that we will always call with a single
// type whereas it has two parameters. This leads to the first or the
// second version of the clear_if_clear_available, depending on the
// syntactical validity of the expression me.clear(), when me is an
// instance of PROCESS.
template<typename PROCESS> void execute_process(const PROCESS& process, unsigned int nb_times) {
clear_if_clear_available<PROCESS> clear_if_allowed {}; // Clear is used in operator() only if it is syntaxically available.
clear_if_allowed(process); // Calls clear, or do nothing according to the template instanciation.
for(unsigned int i = 0; i < nb_times; ++i) process(i);
}
// This process do not need to be cleared beforehand.
void process1(unsigned int arg) {
std::cout << "process1(" << arg << ")" << std::endl;
}
// This process do need to be cleared beforehand.
struct Process2 {
mutable bool cleared = false;
void operator()(unsigned int arg) const {
if(cleared)
std::cout << "Process2(" << arg << ")" << std::endl;
else
throw "Process2 is used whithout previous clear.";
}
void clear() const {
cleared = true; // ok with const, it is mutable.
std::cout << "Process2::clear()" << std::endl;
}
};
int main(int argc, char* argv[]) {
Process2 process2;
std::cout << std::endl;
execute_process(process1, 5);
std::cout << std::endl;
execute_process(process2, 5);
std::cout << std::endl;
return 0;
}