C ++: pattern for checking compilation of an expression

When writing a specialization with SFINAE, you often come to the point where you need to write a whole new specialization due to one small nonexistent element or function. I would like to pack this selection into a small statement like orElse<T a,T b> .

small example:

 template<typename T> int get(T& v){ return orElse<v.get(),0>(); } 

Is it possible?

+6
source share
2 answers

The goal of orElse<v.get(),0>() is clear enough, but if such a thing can exist, it must be one of:

Invocation Lineup

 orElse(v,&V::get,0) orElse<V,&V::get>(v,0) orElse<V,&V::get,0>(v) 

where v is of type v , and the function template created in this way will be respectively:

Functional Template

 template<typename T> int orElse(T & obj, int(T::pmf*)(), int deflt); template<typename T, int(T::*)()> int orElse(T & obj, int deflt); template<typename T, int(T::*)(), int Default> int orElse(T & obj); 

As you know, such a thing cannot exist with the desired effect.

For anyone who doesn't understand this, the reason is this: none of the functions in the Invocation Lineup will compile unless there is a member like V::get . This is not enough, and the fact that the called function can be instantiated in the Function Template Lineup does not matter. If V::get does not exist, then any code that mentions it will not compile.

However, you seem to have a practical goal, which you do not need to approach in this is simply hopeless. It looks like for a given name foo and a given type R , you want to write only one function template:

 template<typename T, typename ...Args> R foo(T && obj, Args &&... args); 

which will return the value of R(T::foo) by calling obj with arguments args... if such a member function exists and otherwise returns the default value of R

If this is correct, this can be achieved in accordance with the following illustration:

 #include <utility> #include <type_traits> namespace detail { template<typename T> T default_ctor() { return T(); } // SFINAE `R(T::get)` exists template<typename T, typename R, R(Default)(), typename ...Args> auto get_or_default( T && obj, Args &&... args) -> std::enable_if_t< std::is_same<R,decltype(obj.get(std::forward<Args>(args)...)) >::value,R> { return obj.get(std::forward<Args>(args)...); } // SFINAE `R(T::get)` does not exist template<typename T, typename R, R(Default)(), typename ...Args> R get_or_default(...) { return Default(); } } //namespace detail // This is your universal `int get(T,Args...)` template<typename T, typename ...Args> int get(T && obj, Args &&... args) { return detail::get_or_default<T&,int,detail::default_ctor> (obj,std::forward<Args>(args)...); } // C++14, trivially adaptable for C++11 

which can be checked with:

 #include <iostream> using namespace std; struct A { A(){}; int get() { return 1; } int get(int i) const { return i + i; } }; struct B { double get() { return 2.2; } double get(double d) { return d * d; } }; struct C{}; int main() { A const aconst; A a; B b; C c; cout << get(aconst) << endl; // expect 0 cout << get(a) << endl; // expect 1 cout << get(b) << endl; // expect 0 cout << get(c) << endl; // expect 0 cout << get(a,1) << endl; // expect 2 cout << get(b,2,2) << endl; // expect 0 cout << get(c,3) << endl; // expect 0 cout << get(A(),2) << endl; // expect 4 cout << get(B(),2,2) << endl; // expect 0 cout << get(C(),3) << endl; // expect 0 return 0; } 

In the complex inverse type there is a "compound SFINAE":

 std::enable_if_t< std::is_same<R,decltype(obj.get(std::forward<Args>(args)...)) >::value,R> 

If T::get does not exist, then decltype(obj.get(std::forward<Args>(args)...) does not compile. But if it compiles and the return type T::get is something other than R , then the std::enable_if_t type specifier is not compiled. Only if there is a member function and has the desired return type R you can create an instance of R(T::get) . Otherwise, catch-all R(T::get) does not exist . case selected.

Note that get(aconst) returns 0, not 1. That should be because the non-constant overload of A::get() cannot be called on const A

You can use the same template for any other R foo(V & v,Args...) and existing or non-existent R(V::foo)(Args...) . If R not constructive by default, or if you want R to default, then it returns when R(V::foo) does not exist to be something other than R() , then define the function detail::fallback (or what something else), which returns the desired default value of R and specify it instead of detail::default_ctor

How nice would it be that you could once again template a template to accommodate any possible member function of T with any possible return type R But the additional template parameter that you will need for this is R(T::*)(typename...) , and its value for creation should be &V::get (or something else), and then the template forces you to mention in a fatal trap a thing whose existence is in doubt.

+2
source

Yes, it is more or less possible. It is known as a member detector. See this link for how to do this with macros. The actual implementation will depend on whether you use pre- or post-C ++ 11 and which compiler you use.

+2
source

Source: https://habr.com/ru/post/983328/


All Articles