0

I have a parsing function that I want to branch based on whether a length option is set or not. If the length option is set, the function should always check if the decreased length equals to 0. If not, it only checks for null termination. Based on this little detail I don't want to rewrite my whole function, so here's what I came up with:

#include <iostream>

const char* str = "some random string";

template <bool LengthOpt = false>
void parse(const char* ch, size_t len = 0)
{
    while ( 1 ) {
        if constexpr(LengthOpt) {
            if ( len == 0 ) {
                std::cout << std::endl;
                return ;
            } else {
                len--;
            }
        } else {
            if ( !(*ch) ) {
                std::cout << std::endl;
                return ;
            }
        }
        std::cout << *ch;
        ++ch;

        /* big part starts here */
    }
}

int main()
{
    parse<true>(str, 5);
    parse(str);
}

godbolt

What bugs me is that I always have to specify both, length AND template parameter to go with the length option. So I'm wondering:

  • Is there a way to constexpr branch based on whether an optional parameter is set or not?
  • Could I infer template parameters from whether the optional parameter is set?

Note: This is a contrived example showing just the detail. I've added comments in the code where the actual parsing would happen.

glades
  • 3,778
  • 1
  • 12
  • 34
  • First of all, why aren't you using `std::string` for your strings? Secondly, why not use overloading rather than a template and default argument? I.e. `void parse(std::string const&); /* Process the whole string */` and `void parse(std::string const&, size_t); /* Process up to a specific length of the string */` But be careful so that the caller doesn't give a length that is longer than the actual string. – Some programmer dude May 26 '22 at 12:50
  • @Someprogrammerdude My code has multiple overloads, one of which is the overload for const char* which I'm showing here. I could use overloads but I don't want to rewrite the function based on this detail (this is a contrived example containing just this detail). All the other stuff stays the same. – glades May 26 '22 at 12:53
  • Or look at how all [standard algorithm functions](https://en.cppreference.com/w/cpp/algorithm) handles this: A pair of iterators. That could handle both your cases with one function. Perhaps it's even possible to use ome of the standard algorithm functions, like [`std::for_each`](https://en.cppreference.com/w/cpp/algorithm/for_each) (or considering your C++17 tag, [`std::for_each_n`](https://en.cppreference.com/w/cpp/algorithm/for_each_n)). – Some programmer dude May 26 '22 at 12:53
  • @Someprogrammerdude But then how do I determine the end of a const char? strlen will increase my time complexity to O(2n) and I don't want that. – glades May 26 '22 at 12:55
  • Perhaps you're doing premature optimizations? Will this be one of the top one or two bottlenecks in your program? A time complexity like O(2n) might look bad, but remember it's not about absolute time measured. Always do the simple, good and maintainable solution first. Then if there are requirements about "efficiency" or timing, you create an optimized build and measure and benchmark that. Fix the top *one* bottleneck. Test, measure and benchmark again. Stop when the requirements are fulfilled. And remember to add lots of comments and documentations about your hand-written optimizations. – Some programmer dude May 26 '22 at 12:59
  • 1
    @Someprogrammerdude yes it is performance ciritical as I'm on an embedded system and the function operates directly on the network buffer. I want to free the network buffer as early as possible. Also, I don't think the optimization is premature as clearly I either have compile time or runtime branching and while both might not differ much in performance the later will never outperform the former. – glades May 26 '22 at 13:03
  • Then the optimizations are probably not *too* premature... ;) Anyway, while still some added overhead, could you still do overloading and then call a helper function for the common functionality? Or use `std::for_each_n` (or `std::for_each`)? How hard is it to keep track of the actual data size you receive? If you could keep track of that then you won't need `strlen`. Perhaps a "buffer" structure that keeps track of both the pointer and the length (perhaps even actual size and capacity)? – Some programmer dude May 26 '22 at 13:08
  • Please specify template requirements by multiple examples how it should be used and what it should do. What is wrong with old fashioned overload? Or why not use `std::string_view` which does similar magic? – Marek R May 26 '22 at 15:57

2 Answers2

1

I think you can use function overloading here:

#include <iostream>

const char* str = "some random string";

void parse(const char* ch, size_t len)
{
    while ( 1 ) {
        if ( len == 0 ) {
            std::cout << std::endl;
            return ;
        } else {
                len--;
        }

        std::cout << *ch;
        ++ch;

        /* the big part can be moved into separate function */
       bigPart(ch);
    }
}

void parse(const char* ch)
{
    while ( 1 ) {
        if ( !(*ch) ) {
            std::cout << std::endl;
            return ;
        }

        std::cout << *ch;
        ++ch;

        /* the big part can be moved into separate function */
       bigPart(ch);

    }
}

int main()
{
    parse(str, 5);
    parse(str);
}
Sandro
  • 2,707
  • 2
  • 20
  • 30
0

You can do this with a pair of overloads that delegate to a templated version to avoid duplicating code:

template <bool LengthOpt>
void do_parse(const char* ch, std::size_t len) {
    // ...
}

void parse(const char* ch) {
    do_parse<true>(ch, 0);
}

void parse(const char* ch, std::size_t len) {
    do_parse<false>(ch, len);
}

Or you can switch to an iterator-based approach:

template<typename Iterator, typename Sentinel>
void do_parse(Iterator it, Sentinel end) {
    for (; it != end; ++it) {
        char c = *it;
        // ...
    }
}

struct null_sentinel_t {};
inline constexpr null_sentinel_t null_sentinel{};

inline constexpr bool operator==(null_sentinel_t, const char* p) noexcept { return *p == 0; }
// The next 3 overloads are not necessary in C++20
inline constexpr bool operator==(const char* p, null_sentinel_t) noexcept { return *p == 0; }
inline constexpr bool operator!=(null_sentinel_t, const char* p) noexcept { return *p != 0; }
inline constexpr bool operator!=(const char* p, null_sentinel_t) noexcept { return *p != 0; }

void parse(const char* ch) {
    do_parse(ch, null_sentinel);
}
void parse(const char* ch, std::size_t len) {
    do_parse(ch, ch + len);
}
Artyer
  • 31,034
  • 3
  • 47
  • 75