3

I have a lot of code that is somehow doing exactly the same operations (inherited code), and I want to, while reworking it, compress code without losing functionality. For example let's look at the following functions:

fnc(largetype & a,  largetype & b)  { f(A); f(B); };
fnc(largetype && a, largetype & b)  { f(A); f(B); };
fnc(largetype & a,  largetype && b) { f(A); f(B); };
fnc(largetype && a, largetype && b) { f(A); f(B); };

All of them are doing exactly the same thing, but the arguments can be rvalues or lvalues without breaking the function logic. I want to allow the user to pass whatever suits the problem, but I also do not want to copy-paste all code piece by piece. I can do something like this:

fnc(largetype & a,  largetype & b)  { f(A); f(B); };
fnc(largetype && a, largetype & b)  { fnc(a,b) };
fnc(largetype & a,  largetype && b) { fnc(a,b) };
fnc(largetype && a, largetype && b) { fnc(a,b) };

which is technically correct, especially with inlining, but seems wrong to me. Is there any other, nicer way to accomplish such an effect?

The only requirements are that types passed as parameters may/will be somehow larger than default memory block size so copy avoidance is crucial. Also there is a non-zero chance that parameter can be smaller but also be a rvalue. Thread safety is optional.

I considered templating these functions, but this is in my opinion also somehow the wrong approach. Templates solve problems with different accepted types. In my case the types are the same, only passed in a different way.

melpomene
  • 84,125
  • 8
  • 85
  • 148
esavier
  • 423
  • 4
  • 13

1 Answers1

5

As @SamVarshavchik commented, this is a usual candidate for perfect forwarding. A simple approach:

template<typename T, typename U>
void fnc(T&& a, U&& b)
{
    f(std::forward<T>(a));
    f(std::forward<U>(b));
}

If the user passes in objects not of type largetype, the error site will be in this function, which can be confusing to the user. To push the error site to the caller's code, we can use SFINAE to constrain the parameter types:

template<
    typename T, typename U,
    typename = std::enable_if_t<
        std::is_same<std::decay_t<T>, largetype>{}
     && std::is_same<std::decay_t<U>, largetype>{}
    >
>
void fnc(T&& a, U&& b)
{
    f(std::forward<T>(a));
    f(std::forward<U>(b));
}

Online Demo

Alternatively, you may want to keep the error site inside of fnc but give a much clearer error message – this can be accomplished with static_assert:

template<typename T, typename U>
void fnc(T&& a, U&& b)
{
    static_assert(std::is_same<std::decay_t<T>, largetype>{},
                  "argument 'a' must be of type 'largetype'");
    static_assert(std::is_same<std::decay_t<U>, largetype>{},
                  "argument 'b' must be of type 'largetype'");

    f(std::forward<T>(a));
    f(std::forward<U>(b));
}

Online Demo

ildjarn
  • 62,044
  • 9
  • 127
  • 211