8

Suppose somewhere in my code is a function foo with a universal reference parameter, which I cannot change:

template<typename T>
auto foo(T&& t) { std::cout<<"general version"<<std::endl; }

Now I want to overload foo for a given class A, and make sure that for any qualifier and reference type of A the overload is called. For this I can brute-forcely provide an overload for all possible qualifications (ignore volatile for now):

auto foo(A & a) { std::cout<<"A&"<<std::endl; }
auto foo(A const& a) { std::cout<<"A const&"<<std::endl; }
auto foo(A && a) { std::cout<<"A &&"<<std::endl; }
auto foo(A const&& a) { std::cout<<"A const&&"<<std::endl; }

Demo. This however scales very badly for more parameters.

Alternatively, I can pass by value, which seems to capture as well all the previous cases:

auto foo(A a) { std::cout<<"A"<<std::endl; }

Demo. Now however large object need to be copied (--at least in principle).

Is there an elegant way around these problems?

Remember that I can't change the universal reference function, so SFINAE and the like is no possibility.

davidhigh
  • 14,652
  • 2
  • 44
  • 75
  • Is writing a wrapper function which forwards arguments to the correct function and using that instead a reasonable solution for you? – TartanLlama Aug 20 '15 at 14:55
  • AFAIK the compiler always looks for the most specific option. What if you add an enable_if> on the "overload"? – Yam Marcovic Aug 20 '15 at 14:59
  • 1
    @TartanLlama: not really. In the code the corresponding function is `operator&&` (I just took `foo` as a simple example). It's a library and that is too erroneous. – davidhigh Aug 20 '15 at 14:59
  • 2
    @YamMarcovic that would just result in an ambiguous call. – TartanLlama Aug 20 '15 at 15:00
  • @TartanLlama Isn't ambiguity only the result of 2 functions that are ranked the same? Would that happen here? – Yam Marcovic Aug 20 '15 at 15:01
  • @YamMarcovic [Yes](http://coliru.stacked-crooked.com/a/85aa30efac7c762a). – TartanLlama Aug 20 '15 at 15:03
  • @TartanLlama Cool, thanks. – Yam Marcovic Aug 20 '15 at 15:03
  • 1
    @YamMarcovic The partial ordering is done on the parameter types rather than considering `std::enable_if` constraints or similar. Fortunately Concepts will have ordered constraints to solve this issue :). – TartanLlama Aug 20 '15 at 15:06
  • 1
    Is there anything *in particular* you want to do with the `A` within the function? Say, construct a copy of it? It is somewhat rare you want to both consume an rvalue ref and a non-const lvalue ref, unless you are doing something like creating a copy. – Yakk - Adam Nevraumont Aug 20 '15 at 19:31
  • @Yakk: that's a good point. Indeed somewhere in the end a copy is done. Thus I can use the second version above, that is, make a copy once in the function above and `std::move` it further. In this way only the position of the copy is moved. – davidhigh Aug 20 '15 at 19:50

1 Answers1

7

Update for C++20: The answer below remains true for C++11 through C++17, but in C++20 you can do this:

template <typename T>
    requires std::same_as<std::remove_cvref_t<T>, A>
auto foo(T&& t) {
    // since this is more constrained than the generic forwarding reference
    // this one should be preferred for foo(A{})
}

Which you can get using more convenient syntax by creating a named concept:

template <typename T, typename U>
concept DecaysTo = std::same_as<std::decay_t<U>, T>;

// longest form
template <typename T> requires DecaysTo<T, A> void foo(T&&);

// middle form
template <DecaysTo<A> T> void foo(T&&);

// abbreviated form
void foo(DecaysTo<A> auto&&);

Honestly, I think you're out of luck here. The typical approaches all fail. You can do...

SFINAE?

template <typename T> auto foo(T&& );
template <typename T,
          typename = only_if_is<T, A>>
auto foo(T&& );

foo(A{}); // error: ambiguous

Write a class that takes an l-or-rvalue reference?

template <typename T> lref_or_ref { ... };
    
template <typename T> auto foo(T&& );
auto foo(lref_or_ref<A> );

foo(A{}); // calls general, it's a better match

The best you could do is introduce a forwarding function using a chooser:

template <int I> struct chooser : chooser<I - 1> { };
template <> struct chooser<0> { };

template <typename T>
auto bar(T&& t, chooser<0> ) {
    // worst-option, general case
    foo(std::forward<T>(t));
}

template <typename T,
          typename = only_if_is<T, A>>
auto bar(T&& t, chooser<1>) {
    // A-specific
}

template <typename T>
auto bar(T&& t) {
    bar(std::forward<T>(t), chooser<20>{});
}

But you mentioned in a comment that this doesn't work for you either.

Barry
  • 286,269
  • 29
  • 621
  • 977
  • It is possible to get at least the lvalues with `template auto foo(T& t);`, but there doesn't seem to be a way to do that for rvalues. Unless you use concepts, that is (where you can define a pure deduced rvalue reference parameter), but then you can use the easier solution to get both via a constrained forwarding ref. – dyp Aug 20 '15 at 15:24