Home > front end >  Macro to repeat a single character multiple number of times in C
Macro to repeat a single character multiple number of times in C

Time:04-10

I am learning about macros and i want to know that whether it is possible to create a macro that repeats a given character literal a given number of times. For example:

Input:

repeatMacro('a', 5)

should produce(expand to) the output:

Output:

aaaaa

Similarly, repeatMacro('r', 2) should produce

rr

If possible, repeatMacro('r') should produce(expand to):

r

I have not worked with macros before so i don't know if this is a very basic thing to do.

I also don't know if there is any other way(other than macros) to do the same. If there is any other way(other than macro) to do this, i would prefer that way instead of using macros.

CodePudding user response:

This can be done during constant evaluation (compile-time) without using macros at all, using the technique described here. (See the couple of episodes before as well, to get a better context.)

While std::string can be used at compile time, in this particular case a std::array will suffice to do most of the heavy lifting, i.e. the creation of the repeated characters

template<int Size, char Char>
constexpr auto create_array() 
{
    std::array<char, Size> a;
    std::fill(std::begin(a), std::end(a), Char);
    return a;
}

Now, we can make a std::string_view from this std::array quite easily

template<int Size, char Char>
constexpr auto repeat() 
{
    constexpr auto a = create_array<Size, Char>();
    auto const & static_a = make_static<a>();
    return std::string_view(std::begin(static_a), std::end(static_a));
}

Note the make_static helper function. The reason this function is needed is that a is a local variable, and returning a string_view to that data will lead to a dangling reference. The data needs to have static storage duration in order to create a string_view from it.

The implementation of make_static is really straightforward, and relies on the fact that non-type template parameters of class type have static storage duration, so a reference to this parameter can be passed around safely.

template<auto Data>
constexpr auto const & make_static() 
{
    return Data;
}

and now you get a string that can be used at compile time

static_assert(repeat<5, 'a'>() == "aaaaa"sv);

Here's a demo.

Note that in this example, the size and char are passed as template parameters to repeat, but the general technique doesn't rely on that, as arguments can be passed to functions and used as constant expressions, by simply wrapping it in a constexpr lambda.

CodePudding user response:

The preprocessor doesn't have any way to do anything with character literals like 'a' other than output them as is, so I don't think that form is possible. Instead, we'll need to work with identifier preprocessor tokens like a.

Working with numbers in the preprocessor is always tricky, but Boost.Preprocessor has tools to help.

#include <boost/preprocessor/seq/cat.hpp>
#include <boost/preprocessor/repetition/repeat.hpp>
#include <boost/preprocessor/facilities/overload.hpp>

#define REPEAT_MACRO_SEQ_ELEM(z, n, data) (data)
#define REPEAT_MACRO_2(c, n) \
    BOOST_PP_SEQ_CAT(BOOST_PP_REPEAT(n, REPEAT_MACRO_SEQ_ELEM, c))
#define REPEAT_MACRO_1(c) c

#define REPEAT_MACRO(...) BOOST_PP_OVERLOAD(REPEAT_MACRO_, __VA_ARGS__)(__VA_ARGS__)

I've renamed your macro REPEAT_MACRO, because it's common practice to use only uppercase for a preprocessor macro name, giving a hint to the code reader that it's a macro.

REPEAT_MACRO_2 uses BOOST_PP_SEQ_CAT to paste together a sequence of preprocessor tokens. It expects input in the form of a "sequence" like (a)(a)(a)(a)(a), so we pass it the result of BOOST_PP_REPEAT, using REPEAT_MACRO_SEQ_ELEM as the operator to add the parentheses around the input token.

To take care of allowing REPEAT_MACRO(r) producing just r, there's BOOST_PP_OVERLOAD. It will paste the number of arguments onto the end of the given macro prefix REPEAT_MACRO_, so that REPEAT_MACRO(c) calls REPEAT_MACRO_1(c) and REPEAT_MACRO(c,n) calls REPEAT_MACRO_2(c,n).

This solution doesn't really require that the input identifier is just one character, and I'm not sure there's any way to require that. So you also get that REPEAT_MACRO(xyz, 3) gives xyzxyzxyz.

  • Related