Search code examples
cboostc-preprocessor

Dont expand BOOST_PP_SEQ_FOR_EACH if SEQ is empty


If the argument list is empty

#define key_evaluate(r, key, sig) key |= 1 << sig;
#define getKey(...)\
({\
      ComponentKey key = 0;\
      BOOST_PP_SEQ_FOR_EACH(key_evaluate, key, BOOST_PP_VARIADIC_TO_SEQ(__VA_ARGS__))
      key;\
})
    
int main(void)
{
     getKey();
}

expands to

int main(void)
{
    ({ 
         ComponentKey key = 0; 
         key |= 1 << ;
         key; 
    })
}

but I would like to have it expand to nothing if _VA_ARGS_ is empty. This answers a question of this type, but if I use this instead

#define key_evaluate(r, key, sig) key |= 1 << sig;
#define convertToKey(...) BOOST_PP_SEQ_FOR_EACH(key_evaluate, key, BOOST_PP_VARIADIC_TO_SEQ(__VA_ARGS__))
#define components_convertToKey(...)\
    ({\
        ComponentKey key = 0;\
        BOOST_PP_IF(BOOST_PP_EQUAL(BOOST_PP_TUPLE_SIZE((,##__VA_ARGS__)), 1), \
        BOOST_PP_EXPAND, convertToKey) (__VA_ARGS__) \
        key;\
    })

I get this error: error: ‘BOOST_PP_EXPAND’ undeclared. This means BOOST_PP_EXPAND is not replaced by the Preprocessor, because BOOST_PP_EXPAND is used elsewhere, which means it is defined.


Solution

  • I don't know.

    I DO know

    • that macros are often problematic
    • that braced-group expressions are non-standard

    It looks like you're trying to combine a number of integral sigs into a key mask, so making up an example could look like:

    enum SigKeys {
        key0 = 1,
        key1 = 2,
        key2 = 4,
        key3 = 8,
        key4 = 16,
    };
    

    Live On Coliru

    #include <iostream>
    int main(void) {
        std::cout << "key1, key2: " << (key1 + key2) << " == " << getKey(1, 2) << "\n";
        std::cout << "key2, key4: " << (key2 + key4) << " == " << getKey(4, 2) << "\n";
    }
    

    Prints

    key1, key2: 6 == 6
    key2, key4: 20 == 20
    

    RETAKE, NO MACROS

    Assuming your compiler is up to snuff, you could simply write that as:

    template <typename... T>
    constexpr ComponentKey getKey(T&&... sig) {
        return (0 | ... | (1 << sig));
    }
    

    It will be standard, portable, compile in a fraction of the time, be more readable, contexpr and optimizable.

    See it Live On Coliru

    What If Your Compiler Old

    Even then you can have it better:

    ComponentKey getKey(std::initializer_list<int> sig) {
        return std::accumulate(sig.begin(), sig.end(), 0,
                [](int r, int s) { return r | (1 << s); });
    }
    

    See it Live On Coliru as well. Unsurprisingly it will optimize just the same.

    If you prefer without std::accumulate: Coliru and Compiler Explorer

    ComponentKey getKey(std::initializer_list<int> sig) {
        ComponentKey r = 0;
        for (auto& s : sig)
            r |= 1 << s;
        return r;
    }