On 14.05.2019 19:52, Paavo Helde wrote:
> On 14.05.2019 16:56, Thiago Adams wrote:
>>
>> C++ is always moving into a directing of removing the need for
>> preprocessor.
>> Is it still necessary? Where do you need/use macros?
>>
>> I am not considering #ifdef, #error etc, just macro expansion.
>
> 1. For __FILE__, __LINE__:
>
> #define MY_ASSERT(x) \
> if (!(x)) throw AssertException(__FILE__, __LINE__, #x);
>
> MY_ASSERT(n>0);
Consider using a macro to generate an instance of a class like <url:
https://en.cppreference.com/w/cpp/experimental/source_location>, and
pass that to the exception constructor. It centralizes the handling of
source location information. More DRY, Don't Repeat Yourself.
Maybe it will be part of C++23?
Of course, with that class' magic one will be able to ditch the macro.
> 2. For avoiding forgetting to define a mutex lock variable:
>
> #define BOOST_MUTEX_SCOPED_LOCK(mx) \
> boost::mutex::scoped_lock boost_mutex_lock(mx)
>
> boost::mutex mx1;
> BOOST_MUTEX_SCOPED_LOCK(mx1);
>
> // instead of non-functional
> // boost::mutex::scoped_lock(mx1);
Instead of one specific macro for each kind of thing one wants to have
constructor and destructor execution of, I suggest using just one or two
general macros.
C# and Java have this "with... some object"-concept as core language
features.
E.g. write
PH_WITH(boost::mutex::scoped_lock(mx)) {
// Code here
}
Can be defined in C++17 like
/// \brief Binds the specified declarator to `auto&& _` in the following
braces block.
///
/// `$with` can be used to make clear that an object is used for
constructor + destructor
/// execution (RAII), or that it's used just to hold a temporary result
in order to access
/// parts of it using the `_` name.
#define PH_WITH( ... ) \
if ( auto&& _ [[maybe_unused]] = __VA_ARGS__; true )
/// \brief Binds the specified declarator to `const auto& _` in the
following braces block.
///
#define PH_WITH_CONST( ... ) \
if( const auto& _ [[maybe_unused]] = __VA_ARGS__; true )
> 3. For dispatching execution to templates at run time:
>
> #define DISPATCH_NUMERIC(ELEMTYPE,FUNCTION,ARGS) \
> switch(ELEMTYPE) {\
> case UInt8: {typedef uint8_t dispatch_t; FUNCTION ARGS; break;}\
> case UInt16: {typedef uint16_t dispatch_t; FUNCTION ARGS; break;}\
> case UInt32: {typedef uint32_t dispatch_t; FUNCTION ARGS; break;}\
> case UInt64: {typedef uint64_t dispatch_t; FUNCTION ARGS; break;}\
> case Int8: {typedef int8_t dispatch_t; FUNCTION ARGS; break;}\
> case Int16: {typedef int16_t dispatch_t; FUNCTION ARGS; break;}\
> case Int32: {typedef int32_t dispatch_t; FUNCTION ARGS; break;}\
> case Int64: {typedef int64_t dispatch_t; FUNCTION ARGS; break;}\
> case Float: {typedef float dispatch_t; FUNCTION ARGS; break;}\
> case Double: {typedef double dispatch_t; FUNCTION ARGS; break;}\
> default: throw Exception("Not implemented");\
> };
>
> template<typename T> foo(const T* source, size_t len, int param1);
>
> DISPATCH_NUMERIC(
> myvec.ElemType(), foo, (myvec.Data<dispatch_t>(), myvec.Size(), 42));
If you want to get rid of the C11 style macros, consider instead
something like
template< class F >
void dispatch_id_to_type( const int type_id, const F& f )
{
switch( type_id )
{
case Number_type::uint8_id: return f( uint8() );
case Number_type::uint16_id: return f( uint16() );
// ...
}
}
Here template parameter F will generally be the type of a lambda, like
from the call
dispatch_id_to_type(
myvec.ElemType(),
[&]( auto x ) {
using T = decltype( +x );
foo( myvec.Data<dispatch_t>(), myvec.Size(), 42 )
}()
);
Disclaimer: off the cuff code.
[snip]
Cheers!,
- Alf