Search code examples
c++c++20c++-concepts

Avoiding repetition with concepts


When writing overloads for r-value I often end up with the same code. So to avoid repetition I use this trick

void f(int&&) {
    std::println("&& overload");
}
void f(int const&) {
    std::println("const& overload");
}

template<typename T1, typename T2>
concept no_repetition = std::same_as<std::remove_cvref_t<T1>, T2>;

void test(no_repetition<int> auto&& val) {
    f(std::forward<decltype(val)>(val));
}

int main( ) {
    int lval = 0;
    test(0);    //prints && overload
    test(lval); //prints const& overload
    return 0;
}

I am curious whether this approach has caveats. And if it does how can work around them.


Solution

  • You may have oversimplified your question, but in your example the test() function is more restrictive than the f(...) functions. Specifically:

    double penetration = 69.;
    
    f(69.);            // works
    f(penetration);    // works, but f(int&&) is invoked !!
    
    test(69.);         // fails
    test(penetration); // fails
    

    You can rewrite your function using universal references:

    void test5(auto&& val)
    {
        f(std::forward<decltype(val)>(val));
    }
    

    which will behave the same way as the f(...) functions:

    test5(69.); // works
    test5(penetration); // works, but invokes f(int&&) !!
    

    If you do want your test() function to have a hard constraint on int, then you have already come up with the best solution. In your comments you've mentioned that you don't understand how it works, so here is a quick explanation. If you have a concept, say:

    template<typename T, typename U>
    concept Uref = std::same_as<std::remove_cvref_t<T>, U>;
    

    you can write an abbreviated function-template, like you did:

    void test1(Uref<int> auto&& val)
    {
        f(std::forward<decltype(val)>(val));
    }
    

    This is equivalent to:

    template< Uref<int> Int >
    void test2(Int&& val)
    {
        f(std::forward<Int>(val));
    }
    

    Notice that in the above example (as well as in the abbreviated function-template) you specify one less parameter in Uref<int> because the deduced type (Int) is implicitly substituted as the first parameter to Uref.

    You could also re-write the above in a more verbose form:

    template<typename Int>
      requires Uref<Int, int>
    void test3(Int&& val)
    {
        f(std::forward<Int>(val));
    }
    

    Link to working example: https://godbolt.org/z/76hP3P5zz