Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Zero-cost lists for inline functions in c++

I like writing checks for a function over a list. For this I usually write a function like this:

inline bool good_strings(const std::vector<const char *> & items)
{
    for (i in items) {
        if (not is_good(i)) return false;
    }
    return true;
}

Then I can write like if (all_good({"a", "b", "c", "d", "e"})) {...} and it looks really nice. This is good to use when your check for a couple of items grows bigger like this:

if (is_good("a") and is_good("b") and /* that's too much, man */ is_good("c")) {...}

But I'm concerned with the overhead of the container I'm using, and also it's hard to choose one: std::vector, std::list, QList, QStringList or maybe even std::array or std::initializer_list - which should be used for inline functions? And which of these a has minimum or even zero overhead when creating using the {} brackets?

Alright, and update: I grabbed my friend licensed IDA Pro and checked some options.

  • std::initializer_list: the function doesn't even inline, and there is overhead for creating the list and copying pointers.
  • std::vector: the function does inline, however, there is an overhead for creating a vector and copying pointers there.
  • std::array: not as good-looking because of template specialization, and the function doesn't inline. So calling it many times creates many similar chunks of code. However, there is no overhead for array creation, and all pointers are passed as function parameters, which is fast for x86_64 register calling a convention.

The question remains, is there an absolutely zero-cost container?

like image 734
MorJ Avatar asked Feb 04 '19 18:02

MorJ


1 Answers

None of the containers are going to be zero overhead. std::array or std::initializer_list will give you the least amount of cost though. std::array needs it's type and size specified at compile time so it is a little less user friendly then a std::initializer_list in this case. So, using a std::initializer_list<const char*> will be the smallest and easiest to use "container" you can use. It will cost the size of the array of pointers the compiler generates and possibly a little more and it won't require any dynamic memory allocation.


If you can use C++17 You don't even need a container. Utilizing a variadic template and a fold expression you can have all the arguments passed to the function as separate parameters and the apply the same operation to all of the arguments.

template<typename... Args>
bool good_strings(Args&&... args)
{
    return (is_good(args) && ...);
}

will turn

all_good("a", "b", "c", "d", "e")

into

return is_good("a") && is_good("b") && ... && is_good("e");

which leverages short circuiting so it will stop evaluating as soon as the first call to is_good returns false.

You can utilize the variadic template in C++11, but you would either need to use recursion, or build your own array, which really doesn't gain you anything with the extra complexity you would have.

like image 126
NathanOliver Avatar answered Sep 17 '22 22:09

NathanOliver