super generic functions in c++14

Since C++14 lambdas now allow auto parameters, function objects can be pretty damn generic.

[](auto&& arg1, auto&& arg2) { return arg1 < arg2; }

Which got me thinking, what’s to stop me from defining all of my functions as lambdas. For example, this function which takes an iterable and prints it to stdout.

auto print_iterable = [](auto&& iterable) {
    for (auto&& e : iterable) {
        std::cout << e << '\n';
    }
};

int main() {
    std::vector<int> v{1,7,2,0};
    print_iterable(v);
}

This function can deduce its argument types and return type. There’s no return here of course, but there’s nothing stopping me from making this function more complex.

I haven’t thought too much about the implications of this, but it seems like I could get away without naming variable types or using templates. I’m not saying this a good idea, but it’s an interesting one.

Advertisement

Applying an operation on each element in a tuple

I ran into this when implementing the C++14 version of zip for cppitertools.

Given a tuple of iterators, increment each of those iterators using ++. Assuming I already have a std::index_sequence with the indices as Is..., I initially had something like this:

void increment_all() {
    ++std::get<Is>(this->tup)...;
}

However, this doesn’t work because the expansion cannot appear outside of a function call or initializer list. This can be worked around with a function that absorbs everything. My original implementation was to make it variadic like so:

template <typename... Ts>
void absorb(Ts&&...) { }

this isn’t really necessary though, since we have the older … that works just as well. The below meets the requirements:

//void absorb(...) {} //UPDATE DON'T USE THIS READ BELOW
 
void increment_all() {
    absorb(++std::get<Is>(this->tup)...);
}

And this works for my purposes. Note that this is a function call, so the order in which the iterators will be incremented is unspecified. If it’s important to you for it to happen in a 0, 1, …, N order, then you can use an initializer list if all of the operations will return the same type, or use other solutions if they don’t.

UPDATE: I reverted to using the templated absorb because the version with just … will attempt to make copies of its input arguments. This is not what I ever want to happen. Even though the copies would be optimized out, it still prevents the function from working with non-copyable (in some cases, only non-movable) types.