📜 ⬆️ ⬇️

Template magic, IsValidExpression metafunction

Good day, dear Habrasoobschestvo.

Today I want to share one interesting trick that allows you to determine the compilability of any particular expression.

Example:
/ * We define the HasF metafunction, which allows us to determine the presence of the f () function in any class. * /
DECLARE_IS_VALID_EXPRESSION (
HasF,
( ( U * ) NULL ) - > f ( ) / * This expression is compiled only if U :: f () * / ) is present ;
')
struct Foo { void f ( ) ; } ;
struct Bar { } ;

BOOST_STATIC_ASSERT ( HasF <A> :: value ) ; / * The HasF <A> :: value constant will be true * /
BOOST_STATIC_ASSERT ( ! HasF < B > :: value ) ; / * Here the HasF constant <A> :: value will be false * /

As you probably already guessed, we will think how to write the macro DECLARE_IS_VALID_EXPRESSION.

So, our goal is to learn how to determine whether an expression will compile or not. At the same time, the compiler, naturally, should not produce any errors: a constant should simply be generated, with a value of 0 if the expression is not compiled, and a value of 1 otherwise.

Release


For this we will use the principle of SFINAE (substitution failure is not an error). In human language, this means that if the compiler encounters an “error” inside the template definition (not in the template body, but in the definition, that is, in those places where the compiler tries to “match” the template parameters that are adequate to the code), then this “error "Does not lead to a compilation error, but to the termination of an attempt to instantiate a template function (or class) with those parameters that cause an" error ".

This is how the following code works:
$ define DECLARE_IS_VALID_EXPRESSION ( NAME, U_BASED_RUNTIME_EXPRESSION ) \
template < class T > \
struct NAME \
{ \
/ * We need some type that is not exactly T for comparison * / \
struct CDummy { } ; \
\
/ * This overload will work only when U_BASED_RUNTIME_EXPRESSION does not contain "errors" \
** Otherwise, this overload will be ignored according to SFINAE. * / \
template < typename U > \
static decltype ( U_BASED_RUNTIME_EXPRESSION ) F ( void * ) ; \
\
/ * But this overload is always present, but its priority is lower, because the ellipsis * / \
template < typename U > \
static CDummy F ( ... ) ; \
\
/ * This typedef might not have been, but without it, this class does not work correctly :( \
** (I take this opportunity to say hello to testers from the Visual Studio command) * / \
typedef decltype ( F < T > ( nullptr ) ) \
TDummy ; \
\
enum \
{ \
/ * value will be 1 if U_BASED_RUNTIME_EXPRESSION does not contain "errors" and 0 otherwise \
** Why? \
** If there are no "errors", then both versions of F are present, and F <T> (nullptr) selects one, \
** in which there is no ellipsis, i.e. with our test expression, and its return type
** not CDummy, because CDummy is declared locally. \
** If there are "errors", then option F with the tested expression will be thrown out, and, \
** respectively, F <T> (nullptr) will select the second overload (which returns CDummy) * / \
value = ! boost :: is_same < CDummy, TDummy > :: value \
} ; \
} ;

This implementation, unfortunately, requires C ++ 0x (my compiler is VC10). It is theoretically possible to do without a new standard (the idea is the same, but instead of decltype sizeof is used). But! Here again I want to say hello to testers from Microsoft, because sizeof does not work correctly in the template definition area - it is “not expected” there (if I remember correctly). In gcc, the sizeof solution works fine.

Application


An example of application is, for example, the following code:
/ * Specifies the IsStreamSerializationSupported metafunction, which returns
** true if the argument supports input / output through threads * /
DECLARE_IS_VALID_EXPRESSION (
IsStreamSerializationSupported,
( ( std :: cout << * ( U * ) NULL ) , ( std :: cin >> * ( U * ) NULL ) ) ) ;

/ * double supports I / O through out-of-box * /
BOOST_STATIC_ASSERT ( IsStreamSerializationSupported < double > :: value ) ;

struct Foo { } ;

/ * But Foo I / O through streams does not support :( * /
BOOST_STATIC_ASSERT ( ! IsStreamSerializationSupported < Foo > :: value ) ;

struct Bar { } ;

template < class TChar, class Traits >
std :: basic_ostream < TChar, Traits > & operator << (
std :: basic_ostream < TChar, Traits > & , const Bar & ) ;

template < class TChar, class Traits >
std :: basic_istream < TChar, Traits > & operator >> (
std :: basic_istream < TChar, Traits > & , Bar & ) ;

/ * Bar supports in / out through streams, since The corresponding operators are defined. * /
BOOST_STATIC_ASSERT ( IsStreamSerializationSupported < Bar > :: value ) ;

Such pieces help in verifying the compliance of the type transferred to a template with different concepts (in this case, to meet the concept, the type must support input / output through the streams).

For this, I say goodbye, thank you all for your attention! :)

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


All Articles