Select unary vs. binary std::transform function overload automatically based on callable's signature
Asked Answered
F

1

7

std::transform provides overloads which take either a unary (one argument) or binary (two argument) callable operation (typically a lambda).

I would like to pass my desired callable as an argument to a parent function, and use a compile-time (e.g. template metaprogramming) approach to automatically select which of the std::transform overloads gets used, based on whether the passed callable has a function signature with one or two arguments.

Here is the desired approach expressed in (not yet working) code:

#include <algorithm>

auto UnaryOp = [](const auto& src) { return src; };                                 // simple copy
auto BinaryOp = [](const auto& src1, const auto& src2) {return src1 + src2; };      // accumulate

auto GenericTransformer = [](auto src, auto dst, auto operation) {  // operation is unary OR binary

    /* unrelated code */

        // need to chose this one:
    std::transform(src.begin(), src.end(), dst.begin(), operation);
        // or this one:
    std::transform(src.begin(), src.end(), dst.begin(), dst.begin(), operation);
        // depending on whether unary or binary operation is passed in 'operation' argument

    /* unrelated code */

};

int main() {
    std::vector<int> source_vec(100);
    std::vector<int> dest_vec(100);
    GenericTransformer(source_vec, dest_vec, UnaryOp);  // i.e. copy source to destination
    GenericTransformer(source_vec, dest_vec, BinaryOp); // i.e. accumulate source into destination
}

Here I have defined two lambda operations - one unary and one binary (UnaryOp and BinaryOp) – which are passed to the GenericTransformer() from main().

Within GenericTransformer(), what compile-time magic can I use to automatically select which of the two std::transform() calls gets made based on the function signature of the operation argument?

NOTE: this is a simplified case for example purposes. I would prefer not to have to split GenericTransformer() into two separate functions (a unary one and a binary one), as that would result in a lot of duplication of code not shown here. Sticking to the DRY philosophy!

Fenella answered 30/12, 2019 at 16:54 Comment(0)
A
7

With C++17 you can mix if constexpr and std::is_invocable:

if constexpr (std::is_invocable_v<
    decltype(operation),  decltype(*src.begin())>) {
    std::transform(src.begin(), src.end(), dst.begin(), operation);
}
else {
    std::transform(src.begin(), src.end(), dst.begin(), dst.begin(), operation);
}

You could also check that operation is valid in the second case but that would require an extra else branch to avoid silencing compile-time errors when (neither branch is valid), and this would require some always_false shenanigan.

Araby answered 30/12, 2019 at 17:7 Comment(2)
@StoryTeller-UnslanderMonica That's right, I've removed the branch and use a else instead of else if to avoid silently ignoring the if.Araby
Thanks @Holt... yes, the std::transform template will cause compile time error if an unsupported operation is passed, so the simple if/else case covers all the bases.Fenella

© 2022 - 2024 — McMap. All rights reserved.